C#中自动将类克隆为dynamic对象的高效实现方案问询
高效实现类实例到Dynamic对象的自动映射方案
这问题我太感同身受了!手动写字段映射不仅繁琐,后续类结构更新时还得同步改代码,JSON序列化又拖性能,给你几个实用且高效的解决方案:
方案1:反射快速映射(简单易用,适合低频率场景)
直接通过反射遍历类的公共字段/属性,自动赋值到ExpandoObject里,代码量少且能自动同步类结构变化:
public static dynamic ToExpandoObject<T>(T obj, IEnumerable<string> includeMembers = null) { if (obj == null) return null; var expando = new ExpandoObject(); var expandoDict = (IDictionary<string, object>)expando; var includeSet = includeMembers?.ToHashSet(StringComparer.OrdinalIgnoreCase); // 处理公共字段 foreach (var field in typeof(T).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { if (includeSet == null || includeSet.Contains(field.Name)) { expandoDict[field.Name] = field.GetValue(obj); } } // 处理公共属性(可选,按需保留) foreach (var prop in typeof(T).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { if (prop.CanRead && (includeSet == null || includeSet.Contains(prop.Name))) { expandoDict[prop.Name] = prop.GetValue(obj); } } return expando; }
用法示例
C c = new C(); // 映射所有公共成员 dynamic fullD = ToExpandoObject(c); // 只映射指定成员(实现"精简版"需求) dynamic slimD = ToExpandoObject(c, new[] {"a", "b"});
方案2:表达式树编译映射(性能接近手动代码,适合高并发场景)
如果你的API调用频率很高,反射的性能开销可能无法接受,可以用表达式树编译并缓存映射逻辑,第一次调用编译后,后续调用直接用缓存的委托,性能几乎和手动写映射一样:
using System.Linq.Expressions; using System.Reflection; public static class ExpandoMapper { private static readonly Dictionary<Type, Func<object, ExpandoObject>> _mapperCache = new Dictionary<Type, Func<object, ExpandoObject>>(); public static dynamic ToExpando<T>(T obj, IEnumerable<string> includeMembers = null) { if (obj == null) return null; var type = typeof(T); var cacheKey = (type, includeMembers?.OrderBy(x => x).ToString()); if (!_mapperCache.TryGetValue(type, out var mapper)) { mapper = CreateMapper<T>(includeMembers); _mapperCache[type] = mapper; } return mapper(obj); } private static Func<object, ExpandoObject> CreateMapper<T>(IEnumerable<string> includeMembers) { var parameter = Expression.Parameter(typeof(object), "obj"); var castObj = Expression.Convert(parameter, typeof(T)); var expando = Expression.Variable(typeof(ExpandoObject), "expando"); var expandoDict = Expression.Variable(typeof(IDictionary<string, object>), "expandoDict"); var includeSet = includeMembers?.ToHashSet(StringComparer.OrdinalIgnoreCase); var bindings = new List<Expression>(); // 初始化ExpandoObject bindings.Add(Expression.Assign(expando, Expression.New(typeof(ExpandoObject)))); bindings.Add(Expression.Assign(expandoDict, Expression.Convert(expando, typeof(IDictionary<string, object>)))); // 添加字段映射逻辑 foreach (var field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance)) { if (includeSet != null && !includeSet.Contains(field.Name)) continue; var fieldValue = Expression.Field(castObj, field); var fieldValueObj = Expression.Convert(fieldValue, typeof(object)); var addField = Expression.Call( expandoDict, typeof(IDictionary<string, object>).GetMethod("set_Item"), Expression.Constant(field.Name), fieldValueObj); bindings.Add(addField); } // 添加属性映射逻辑(可选) foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!prop.CanRead || (includeSet != null && !includeSet.Contains(prop.Name))) continue; var propValue = Expression.Property(castObj, prop); var propValueObj = Expression.Convert(propValue, typeof(object)); var addProp = Expression.Call( expandoDict, typeof(IDictionary<string, object>).GetMethod("set_Item"), Expression.Constant(prop.Name), propValueObj); bindings.Add(addProp); } // 返回最终的ExpandoObject bindings.Add(expando); var block = Expression.Block(new[] { expando, expandoDict }, bindings); var lambda = Expression.Lambda<Func<object, ExpandoObject>>(block, parameter); return lambda.Compile(); } }
用法示例
C c = new C(); dynamic d = ExpandoMapper.ToExpando(c, new[] {"a", "b"});
方案选择建议
- 如果你只是小流量场景,反射方案足够简单,代码维护成本极低;
- 如果是高并发API,表达式树方案能把性能拉到接近手动映射的水平,且后续类结构更新时无需修改映射代码;
- 两个方案都支持通过
includeMembers参数指定要返回的成员,完美实现你要的"精简版"API返回需求,后续类新增字段时,只要调整这个参数列表即可,不用改动核心映射逻辑。
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

