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

无装箱、无反射的类属性赋值与比较实现方案咨询

无装箱、无反射的类属性赋值与比较实现方案咨询

嘿,你的需求我完全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);
}

方案优势

  1. 完全无反射、无装箱:编译后的委托直接操作属性,值类型的赋值和比较都是强类型操作,不会产生装箱开销,性能和手写每个属性的逻辑几乎一致。
  2. 静态初始化,性能最优:静态构造函数只执行一次,所有委托只编译一次,后续调用都是直接执行原生IL代码,没有额外开销。
  3. 维护方便:要新增/移除属性,只需要在静态构造函数里添加/删除RegisterProperty的调用即可,强类型写法能在编译阶段就发现错误,比手写字符串属性名靠谱多了。
  4. 兼容性好:支持你提到的所有类型——内置值类型、string、枚举、实现IEquatable<T>的结构体,都会自动适配对应的Equals重载。

额外说明

  • 静态集合是线程安全的:CLR保证静态构造函数的执行是线程安全的,初始化后的集合不会被修改,后续多线程调用CopyTo或IsDirty完全没问题。
  • 如果需要处理可空值类型(比如long?),这个方案也能完美支持,Nullable<T>的Equals方法会正确处理null值的比较,同样不会装箱。

备注:内容来源于stack exchange,提问作者Raheel Khan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 09:58:10