C#中如何通过委托优化反射属性对比的GetValue性能问题
高性能对象属性对比实现方案
原实现的性能损耗来自三个核心点:
- 每次调用对比方法都重复执行
GetProperties反射查询类型元数据 - 高频调用
PropertyInfo.GetValue走反射Invoke逻辑,开销是原生直接调用属性getter的上百倍 - 存在冗余逻辑:已经拿到
PropertyInfo对象的前提下,又重复调用type.GetProperty(pi.Name)二次查询属性,增加无意义开销
优化思路
用类型元数据+取值委托全局缓存方案:
- 每个类型的公开实例属性元数据,仅在第一次对比该类型时反射获取一次
- 通过表达式树为每个属性预编译强类型取值委托,绕开反射
GetValue的调用开销,委托调用性能和手写硬编码取值几乎一致 - 自动兼容所有属性类型:包含int、枚举等值类型(自动处理装箱),string、自定义类等引用类型,无需针对特定类型做适配
完整实现代码
using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; public class ObjectComparer { // 全局缓存:key=待对比类型,value=该类型所有可读属性的取值委托+属性名 private static readonly ConcurrentDictionary<Type, IReadOnlyList<PropertyCompareEntry>> _propertyCache = new(); /// <summary> /// 属性对比条目缓存 /// </summary> private class PropertyCompareEntry { public string PropertyName { get; set; } public Func<object, object> ValueGetter { get; set; } } /// <summary> /// 获取指定类型的属性对比缓存条目,不存在则构建 /// </summary> private static IReadOnlyList<PropertyCompareEntry> GetPropertyEntries<T>() { var targetType = typeof(T); return _propertyCache.GetOrAdd(targetType, type => { // 过滤出所有公有的、可读的、非索引器的实例属性 var validProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(pi => pi.CanRead && pi.GetIndexParameters().Length == 0); var entryList = new List<PropertyCompareEntry>(); foreach (var prop in validProperties) { // 构建表达式树:(object instance) => (object)((T)instance).Prop var instanceParam = Expression.Parameter(typeof(object), "instance"); var castedInstance = Expression.Convert(instanceParam, type); var propAccess = Expression.Property(castedInstance, prop); var boxedValue = Expression.Convert(propAccess, typeof(object)); // 值类型自动装箱 var valueGetter = Expression.Lambda<Func<object, object>>(boxedValue, instanceParam).Compile(); entryList.Add(new PropertyCompareEntry { PropertyName = prop.Name, ValueGetter = valueGetter }); } return entryList; }); } /// <summary> /// 对比两个同类型对象的属性值,返回值不相等的属性名集合 /// </summary> public List<string> Compare<T>(T A, T B) { if (A == null) throw new ArgumentNullException(nameof(A)); if (B == null) throw new ArgumentNullException(nameof(B)); var propertyEntries = GetPropertyEntries<T>(); var unequalProps = new List<string>(); foreach (var entry in propertyEntries) { object valA = entry.ValueGetter(A); object valB = entry.ValueGetter(B); // 保持和原逻辑一致的相等判断规则 if (valA == valB) continue; if (valA == null || !valA.Equals(valB)) { unequalProps.Add(entry.PropertyName); } } return unequalProps; } }
性能说明
- 冷启动(第一次对比某类型)时会一次性完成该类型所有属性的反射和委托编译,耗时略高于原实现
- 热启动(同类型后续对比)时无任何反射开销,取值性能比原
GetValue实现高50~100倍,遍历十万级以上对象列表时不会出现明显性能滞后 - 自动过滤索引器属性,修复原代码遇到索引器时调用
GetValue抛异常的潜在问题 - 可直接扩展:需要忽略特定属性时,可在构建缓存阶段通过自定义特性、属性名黑名单等方式过滤即可
内容的提问来源于stack exchange,提问作者Vignesh G
相关产品推荐
相关产品推荐

