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

如何通过C#表达式实现字典向对象赋值?仅执行一次反射

用表达式树优化反射赋值,只做一次反射操作

你的问题很典型——反射虽然灵活但每次调用都有性能开销,用表达式树提前编译好赋值逻辑并缓存,就能完美解决“只执行一次反射”的需求。咱们可以这么实现:

核心思路是为每个实体类型提前构建并缓存一个赋值委托,第一次处理某个类型时完成反射和表达式树的构建,后续直接调用编译好的委托就行,完全不用再碰反射。

完整实现代码

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

public static class PropertySetter
{
    // 缓存每个类型对应的赋值委托,key是实体类型,value是编译好的赋值动作
    private static readonly Dictionary<Type, Action<object, Dictionary<string, object>>> _setterCache = new();

    internal static void SetPropertiesFromDictionary(object entity, Dictionary<string, object> values)
    {
        if (entity == null) throw new ArgumentNullException(nameof(entity));
        if (values == null) throw new ArgumentNullException(nameof(values));

        var entityType = entity.GetType();
        // 先从缓存取委托,没有的话就构建并缓存
        if (!_setterCache.TryGetValue(entityType, out var setter))
        {
            setter = BuildSetter(entityType);
            _setterCache[entityType] = setter;
        }

        // 直接执行编译好的委托
        setter(entity, values);
    }

    private static Action<object, Dictionary<string, object>> BuildSetter(Type entityType)
    {
        // 参数1:object类型的实体
        var entityParam = Expression.Parameter(typeof(object), "entity");
        // 参数2:Dictionary<string, object>类型的values
        var valuesParam = Expression.Parameter(typeof(Dictionary<string, object>), "values");

        // 将object类型的实体转换为具体的实体类型
        var typedEntity = Expression.Convert(entityParam, entityType);

        // 收集所有属性赋值的表达式节点
        var assignExpressions = new List<Expression>();

        // 只做一次反射,获取目标类型的属性(和你原来的反射逻辑一致)
        var entityProperties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

        foreach (var prop in entityProperties)
        {
            // 构建调用Dictionary.TryGetValue的表达式
            var tryGetValueMethod = typeof(Dictionary<string, object>).GetMethod(nameof(Dictionary<string, object>.TryGetValue));
            var valueVar = Expression.Variable(typeof(object), "value");
            var tryGetValueCall = Expression.Call(
                valuesParam,
                tryGetValueMethod,
                Expression.Constant(prop.Name),
                Expression.Assign(valueVar, Expression.Default(typeof(object)))
            );

            // 构建条件:如果TryGetValue返回true,并且value不为null,就给属性赋值
            var assignProp = Expression.Assign(
                Expression.Property(typedEntity, prop),
                Expression.Convert(valueVar, prop.PropertyType)
            );

            var condition = Expression.AndAlso(
                tryGetValueCall,
                Expression.NotEqual(valueVar, Expression.Constant(null))
            );

            assignExpressions.Add(Expression.IfThen(condition, assignProp));
            assignExpressions.Add(valueVar); // 把变量加入表达式树的变量列表
        }

        // 把所有赋值操作组合成一个块表达式
        var block = Expression.Block(assignExpressions);

        // 构建Lambda表达式并编译成委托
        var lambda = Expression.Lambda<Action<object, Dictionary<string, object>>>(block, entityParam, valuesParam);
        return lambda.Compile();
    }
}

关键细节解释

  • 缓存机制:用_setterCache字典存储每个类型对应的赋值委托,确保同一个类型只会被反射一次,后续直接复用编译好的委托。
  • 表达式树构建:
    • 先把object类型的实体转换为具体的目标类型,避免后续属性访问的类型问题。
    • 对每个属性,构建TryGetValue的调用逻辑,只有当值存在且不为null时才执行赋值。
    • 把所有赋值操作打包成BlockExpression,最后编译成Action<object, Dictionary<string, object>>类型的委托。
  • 性能优势:表达式树编译后的委托性能接近手写的原生赋值代码,比每次循环反射调用SetValue高效得多,尤其在频繁调用的场景下提升明显。

使用方式

和你原来的调用方式完全一样:

var myEntity = new MyEntity();
var values = new Dictionary<string, object> { { "Name", "Alice" }, { "Age", 30 } };
PropertySetter.SetPropertiesFromDictionary(myEntity, values);

这样就完美实现了“仅执行一次反射操作”的需求啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:17:27