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; }
关键优化点
- 缓存复用:所有反射、特性解析、委托创建操作仅在静态构造中执行一次,后续调用直接使用缓存结果。
- 数据结构优化:将列名列表转为
Dictionary,TryGetValue的查询复杂度为O(1),远快于原代码的List.Contains(O(n))。 - 对象创建优化:通过
where T : new()约束直接使用new T()创建对象,性能优于Activator.CreateInstance<T>()。 - 提前初始化容量:
List<T>(table.Rows.Count)避免了集合扩容带来的性能损耗。 - 委托封装转换逻辑:将类型转换逻辑提前封装在委托中,消除循环内的重复类型判断。
额外建议
- 跳过无需映射的属性:不给不需要多列映射的属性添加
Convert特性,缓存类会自动跳过,减少无效操作。 - 批量处理优化:若DataTable数据量极大,可考虑并行处理(注意线程安全,需使用
ConcurrentBag或锁)。 - 基准测试验证:使用BenchmarkDotNet等工具对比优化前后的性能差异,确保达到预期效果。
内容的提问来源于stack exchange,提问作者Azzarrel
相关产品推荐
相关产品推荐

