You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#泛型方法中提取委托优化反射性能的实现方案

问题背景

经过基准测试,我发现自己编写的多数反射方法运行极慢,尤其是在循环迭代中使用时。之前了解到可以为反射方法创建委托来提升性能——验证和安全操作仅在创建委托时执行一次,无需每次循环重复执行。

文章中的性能对比示例如下:
直接调用属性(最优性能):

public class Item
{
  public int Value { get; set; }
}

List<Item> list = Enumerable.Repeat(new Item(), 10000000).ToList();

int i = 0;
foreach (var element in list)
{
  i = element.Value;
  element.Value = 3; 
}

使用反射(性能较差):

PropertyInfo propInfo = typeof(Item).GetProperty("Value");

foreach (var element in list)
{
  i = (int)propInfo.GetValue(element);
  propInfo.SetValue(element, 3);
}

使用委托提升性能:

Action<Item, int> setter = (Action<Item, int>)Delegate.CreateDelegate(
    typeof(Action<Item, int>), null,
    typeof(Item).GetProperty("Value").GetSetMethod());

Func<Item, int> getter = (Func<Item, int>)Delegate.CreateDelegate(
    typeof(Func<Item, int>), null,
    typeof(Item).GetProperty("Value").GetGetMethod());

foreach (var element in list)
{
  i = (int)getter(element);
  setter(element, 3);
}

目前我的部分方法带有泛型类型参数,尚未找到在这类方法中创建委托以实现性能提升的方案。例如以下扩展方法,希望将其中的反射调用替换为委托:

public static void SetValues<T>(this List<T> list)
{
  var propInfos = typeof(T).GetProperties().AsSpan();

  for(int i = 0; i < list.Count; i++) 
  {
    var element = list[i];
    for (int j = 0; j < propInfos.Length; j++)
    {
      var propInfo = propInfos[j];
      if (propInfo.PropertyType == typeof(int) && (int)propInfo.GetValue(element) == default)
        propInfo.SetValue(element, i);
    }
  }
}

我找到一些使用ILGenerator的解决方案,但这类实现过于抽象,希望尽量用纯C#代码完成优化。

补充场景说明

我的实际场景是前端需要连接多个数据库,不同数据库的表列名不一致:

// 准备数据
var list = new List<Item>();
var database = (DatabaseSource)new Random().Next(0, 3);

DataTable source = new DataTable();

switch (database)
{
  case DatabaseSource.db1:
    source.Columns.Add("Val", typeof(int));
    break;
  case DatabaseSource.db2:
    source.Columns.Add("Value", typeof(int));
    break;
  case DatabaseSource.db3:
    source.Columns.Add("IntValue", typeof(int));
    break;
}

source.Rows.Add(5);
source.Rows.Add(2);

手动处理会产生大量冗余代码:

switch (database)
{
  case DatabaseSource.db1:
    foreach(DataRow row in source.Rows)
    {
      list.Add(new Item 
      {
        Value = Convert.ToInt32(row["Val"]) 
        // 更多属性映射...
     });
    }
    break;
  case DatabaseSource.db2:
    foreach (DataRow row in source.Rows)
    {
      list.Add(new Item
      {
        Value = Convert.ToInt32(row["Value"])
        // 更多属性映射...
      });
    }
    break;
  case DatabaseSource.db3:
    foreach (DataRow row in source.Rows)
    {
      list.Add(new Item
      {
        Value = Convert.ToInt32(row["IntValue"])
        // 更多属性映射...
      });
    }
    break;
}

为简化代码,我给模型添加了自定义特性实现自动映射:

public class ConvertAttribute : Attribute
{
  public string[] Columns { get; private set; }
  public ConvertAttribute(string[] columns)
  {
    Columns = columns;
  }
}

public class Item
{
  [Convert(new[] { "Val", "Value", "IntValue" })]
  public int Value { get; set; }
}

随后通过反射实现自动转换:

public static List<T> ConvertToList<T>(this DataTable table)
{
  var list = new List<T>();
  var propInfos = typeof(T).GetProperties();
  var obj = Activator.CreateInstance<T>();
  var columnNames = table.Columns.Cast<DataColumn>().Select(c => c.ColumnName).ToList();
  foreach (DataRow row in table.Rows)
  {
    foreach (var propInfo in propInfos)
    {
      var attributes = propInfo.GetCustomAttributes(inherit: true);
      foreach (var attribute in attributes)
      {
        if (attribute is ConvertAttribute convertAttribute)
        {
          foreach (var columnName in convertAttribute.Columns)
          {
            if (columnNames.Contains(columnName))
            {
              var value = row[columnName];
              if (value.GetType() == propInfo.PropertyType)
                propInfo.SetValue(obj, value);
              else
              {
                if (propInfo.PropertyType.IsEnum)
                  propInfo.SetValue(obj, Enum.ToObject(propInfo.PropertyType, int.Parse(value.ToString())));
                else
                  propInfo.SetValue(obj, Convert.ChangeType(value, propInfo.PropertyType));
              }
            }
          }
        }
      }
    }
    list.Add(obj);
  }
  return list;
}

现在只需一行代码即可完成转换:

var l = source.ConvertToList<Item>();

虽然代码简洁性大幅提升,但性能表现不佳,希望进行优化。同时也想确认当前思路是否存在问题。


解决方案

一、泛型SetValues<T>方法的委托优化

核心思路是利用泛型静态构造的特性缓存委托:每个泛型类型T的静态构造仅执行一次,适合用来一次性创建并缓存所有需要的委托,避免每次调用方法时重复反射。

优化后代码

// 静态类缓存每个T的属性操作委托
private static class PropertyAccessors<T>
{
    // 存储int类型属性的Getter和Setter委托
    public static readonly List<(Func<T, int> Getter, Action<T, int> Setter)> IntPropertyAccessors = new();

    static PropertyAccessors()
    {
        // 一次性获取所有可读写的int类型属性
        var intProps = typeof(T).GetProperties()
            .Where(p => p.PropertyType == typeof(int) && p.CanRead && p.CanWrite)
            .ToList();

        foreach (var prop in intProps)
        {
            // 创建Getter委托
            var getter = (Func<T, int>)Delegate.CreateDelegate(
                typeof(Func<T, int>), 
                prop.GetGetMethod(true)); // true支持访问非公共方法

            // 创建Setter委托
            var setter = (Action<T, int>)Delegate.CreateDelegate(
                typeof(Action<T, int>), 
                prop.GetSetMethod(true));

            IntPropertyAccessors.Add((getter, setter));
        }
    }
}

public static void SetValues<T>(this List<T> list)
{
    // 直接使用缓存的委托,避免循环内反射
    var accessors = PropertyAccessors<T>.IntPropertyAccessors;

    for (int i = 0; i < list.Count; i++)
    {
        var element = list[i];
        foreach (var (getter, setter) in accessors)
        {
            if (getter(element) == default(int))
            {
                setter(element, i);
            }
        }
    }
}

原理说明

  • 静态类PropertyAccessors<T>的静态构造函数会在第一次使用该泛型类型时执行,仅执行一次,完成所有委托的创建和缓存。
  • 后续调用SetValues<T>时直接取用缓存的委托,完全消除循环内的反射操作,性能接近直接调用属性。

二、多数据库列映射场景的性能优化

针对ConvertToList<T>方法,优化方向同样是提前缓存映射规则和委托,避免每次调用重复执行反射、特性解析、列名匹配等操作。

步骤1:创建缓存类存储映射规则与委托

private static class DataTableConverterCache<T> where T : new()
{
    // 存储(列名, 属性赋值委托)的映射关系
    public static readonly List<(string ColumnName, Action<T, object> SetValue)> PropertyMappings = new();

    static DataTableConverterCache()
    {
        var propInfos = typeof(T).GetProperties().Where(p => p.CanWrite);
        var allMappings = new List<(string ColumnName, Action<T, object> SetValue)>();

        foreach (var prop in propInfos)
        {
            // 获取属性上的Convert特性
            var convertAttr = prop.GetCustomAttribute<ConvertAttribute>();
            if (convertAttr == null) continue;

            // 创建封装类型转换逻辑的赋值委托
            var setValueDelegate = CreateSetValueDelegate(prop);

            // 关联特性中所有列名与委托
            foreach (var colName in convertAttr.Columns)
            {
                allMappings.Add((colName, setValueDelegate));
            }
        }

        PropertyMappings = allMappings;
    }

    // 根据属性类型创建对应的赋值委托
    private static Action<T, object> CreateSetValueDelegate(PropertyInfo prop)
    {
        var propType = prop.PropertyType;

        if (propType.IsEnum)
        {
            // 枚举类型:先转int再转枚举
            return (obj, value) =>
            {
                int intValue = value is int ? (int)value : int.Parse(value.ToString());
                prop.SetValue(obj, Enum.ToObject(propType, intValue));
            };
        }
        else if (propType.IsValueType || propType == typeof(string))
        {
            // 值类型或字符串:处理DBNull并转换类型
            return (obj, value) =>
            {
                if (value == DBNull.Value)
                    prop.SetValue(obj, Activator.CreateInstance(propType));
                else
                    prop.SetValue(obj, Convert.ChangeType(value, propType));
            };
        }
        else
        {
            // 引用类型:直接赋值(可扩展复杂转换逻辑)
            return (obj, value) => prop.SetValue(obj, value);
        }
    }
}

步骤2:优化ConvertToList<T>方法

public static List<T> ConvertToList<T>(this DataTable table) where T : new()
{
    var list = new List<T>(table.Rows.Count); // 提前初始化容量,减少扩容开销
    var cache = DataTableConverterCache<T>.PropertyMappings;
    // 将列名转为字典,提升查找效率
    var columnDict = table.Columns.Cast<DataColumn>().ToDictionary(c => c.ColumnName);

    foreach (DataRow row in table.Rows)
    {
        var obj = new T(); // 利用new()约束创建对象,比Activator更快

        foreach (var (colName, setValue) in cache)
        {
            if (columnDict.TryGetValue(colName, out _))
            {
                var value = row[colName];
                setValue(obj, value);
            }
        }

        list.Add(obj);
    }

    return list;
}

关键优化点

  1. 缓存复用:所有反射、特性解析、委托创建操作仅在静态构造中执行一次,后续调用直接使用缓存结果。
  2. 数据结构优化:将列名列表转为Dictionary,TryGetValue的查询复杂度为O(1),远快于原代码的List.Contains(O(n))。
  3. 对象创建优化:通过where T : new()约束直接使用new T()创建对象,性能优于Activator.CreateInstance<T>()。
  4. 提前初始化容量:List<T>(table.Rows.Count)避免了集合扩容带来的性能损耗。
  5. 委托封装转换逻辑:将类型转换逻辑提前封装在委托中,消除循环内的重复类型判断。

额外建议

  1. 跳过无需映射的属性:不给不需要多列映射的属性添加Convert特性,缓存类会自动跳过,减少无效操作。
  2. 批量处理优化:若DataTable数据量极大,可考虑并行处理(注意线程安全,需使用ConcurrentBag或锁)。
  3. 基准测试验证:使用BenchmarkDotNet等工具对比优化前后的性能差异,确保达到预期效果。

内容的提问来源于stack exchange,提问作者Azzarrel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 08:35:58