如何通过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
相关产品推荐
相关产品推荐

