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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:55:50