无装箱、无反射的类属性赋值与比较实现方案咨询
无装箱、无反射的类属性赋值与比较实现方案咨询
嘿,你的需求我完全get到了——想摆脱反射带来的性能开销和装箱问题,同时还能优雅地维护需要处理的属性列表,用表达式树确实是个绝佳的思路!我给你梳理一个落地的实现方案,完全满足你的所有要求:
核心思路
利用表达式树编译生成强类型的委托,在类的静态构造阶段一次性编译完成,后续所有实例调用直接执行编译后的IL代码,彻底避开反射和装箱。每个属性对应两个委托:一个负责把源对象的属性值赋值给目标对象,另一个负责比较两个对象的该属性是否相等,全程强类型操作。
具体实现代码
直接改造你的Model类就行,代码里我加了详细注释:
public partial class Model: ICloneable<Model>, ICopyable<Model>, IDirtyable<Model> { // 静态集合:存储每个属性对应的赋值、比较委托 private static readonly List<(Action<Model, Model> Assign, Func<Model, Model, bool> Compare)> _propertyOperations = new(); // 静态构造函数:一次性编译所有属性的委托,CLR保证只执行一次 static Model() { // 这里维护需要处理的属性列表,强类型写法,不会写错属性名 RegisterProperty(m => m.Id); RegisterProperty(m => m.DateTimeCreated); RegisterProperty(m => m.DateTimeModified); RegisterProperty(m => m.Name); } // 通用方法:编译单个属性的赋值、比较表达式 private static void RegisterProperty<TProp>(Expression<Func<Model, TProp>> propertyExpr) { if (propertyExpr.Body is not MemberExpression memberExpr || memberExpr.Member is not PropertyInfo prop) throw new ArgumentException("请传入有效的属性表达式"); // 1. 生成赋值委托:(target, source) => target.Prop = source.Prop var targetParam = Expression.Parameter(typeof(Model), "target"); var sourceParam = Expression.Parameter(typeof(Model), "source"); var assignExpr = Expression.Assign( Expression.Property(targetParam, prop), Expression.Property(sourceParam, prop) ); var assignAction = Expression.Lambda<Action<Model, Model>>(assignExpr, targetParam, sourceParam).Compile(); // 2. 生成比较委托:(a, b) => a.Prop.Equals(b.Prop) var aParam = Expression.Parameter(typeof(Model), "a"); var bParam = Expression.Parameter(typeof(Model), "b"); var equalsExpr = Expression.Call( Expression.Property(aParam, prop), typeof(object).GetMethod("Equals", new[] { typeof(object) })!, Expression.Property(bParam, prop) ); // 对于实现IEquatable<T>的类型,会自动匹配强类型Equals重载,无装箱 var compareFunc = Expression.Lambda<Func<Model, Model, bool>>(equalsExpr, aParam, bParam).Compile(); _propertyOperations.Add((assignAction, compareFunc)); } // ------------------------------ // 原有的属性定义保持不变 // ------------------------------ public virtual long Id { get; set; } public virtual DateTime DateTimeCreated { get; set; } public virtual DateTime DateTimeModified { get; set; } public virtual string Name { get; set; } = string.Empty; // ------------------------------ // 用委托实现接口方法,替代手写或反射逻辑 // ------------------------------ public Model CopyTo(Model other) { foreach (var op in _propertyOperations) { op.Assign(other, this); } return other; } public bool IsDirty(Model referenceObject) { // 只要有一个属性不相等,就标记为脏数据,提前退出 foreach (var op in _propertyOperations) { if (!op.Compare(this, referenceObject)) { return true; } } return false; } // 其他接口方法保持原有逻辑即可 public Model CloneTyped() => new Model().CopyFrom(this); public Model CopyFrom(Model other) => other.CopyTo(this); }
方案优势
- 完全无反射、无装箱:编译后的委托直接操作属性,值类型的赋值和比较都是强类型操作,不会产生装箱开销,性能和手写每个属性的逻辑几乎一致。
- 静态初始化,性能最优:静态构造函数只执行一次,所有委托只编译一次,后续调用都是直接执行原生IL代码,没有额外开销。
- 维护方便:要新增/移除属性,只需要在静态构造函数里添加/删除
RegisterProperty的调用即可,强类型写法能在编译阶段就发现错误,比手写字符串属性名靠谱多了。 - 兼容性好:支持你提到的所有类型——内置值类型、string、枚举、实现
IEquatable<T>的结构体,都会自动适配对应的Equals重载。
额外说明
- 静态集合是线程安全的:CLR保证静态构造函数的执行是线程安全的,初始化后的集合不会被修改,后续多线程调用
CopyTo或IsDirty完全没问题。 - 如果需要处理可空值类型(比如
long?),这个方案也能完美支持,Nullable<T>的Equals方法会正确处理null值的比较,同样不会装箱。
备注:内容来源于stack exchange,提问作者Raheel Khan
相关产品推荐
相关产品推荐

