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

如何基于属性名列表动态生成Expression<Func<T, object>>表达式

动态生成匿名类型选择器表达式的实现方案

我需要针对给定类型T和属性名列表(比如{"prop1", "prop2"}),生成格式为x => new {x.prop1, x.prop2}的Expression<Func<T, object>>表达式。目前仅能创建单个属性的访问器,但不确定如何处理这种动态生成的匿名类型,现有单个属性的实现代码如下:

private Func<T, int> BuildChildSelectorLambda(string idPropertyName) {
    ParameterExpression argParam = Expression.Parameter(typeof(T), "x");
    Expression selector = Expression.Property(argParam, typeof(T).GetProperty(idPropertyName));

    return Expression.Lambda<Func<T,int>>(selector, argParam).Compile();
    //TODO: CACHE EXPRESSIONS
}

实现思路与代码

要生成动态匿名类型的选择器表达式,核心是动态构建对应属性的匿名类型,并生成初始化表达式。以下是完整实现:

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

public static class ExpressionBuilder
{
    // 缓存已生成的动态类型,避免重复创建
    private static readonly Dictionary<string, Type> _anonymousTypeCache = new Dictionary<string, Type>();
    private static readonly object _cacheLock = new object();

    public static Expression<Func<T, object>> BuildAnonymousSelector<T>(IEnumerable<string> propertyNames)
    {
        var propList = propertyNames.OrderBy(p => p).ToList();
        // 生成缓存键,确保相同属性列表对应同一类型
        string cacheKey = $"{typeof(T).FullName}_{string.Join("|", propList)}";

        Type anonymousType;
        lock (_cacheLock)
        {
            if (!_anonymousTypeCache.TryGetValue(cacheKey, out anonymousType))
            {
                var propertyInfos = propList.Select(name => typeof(T).GetProperty(name, BindingFlags.Public | BindingFlags.Instance))
                                           .Where(p => p != null)
                                           .ToList();

                if (propertyInfos.Count != propList.Count)
                {
                    var missingProps = propList.Except(propertyInfos.Select(p => p.Name));
                    throw new ArgumentException($"类型 {typeof(T).Name} 中不存在以下属性:{string.Join(", ", missingProps)}");
                }

                anonymousType = CreateAnonymousType(propertyInfos);
                _anonymousTypeCache.Add(cacheKey, anonymousType);
            }
        }

        ParameterExpression param = Expression.Parameter(typeof(T), "x");
        // 构建属性访问表达式和成员绑定
        MemberBinding[] bindings = propList.Select(name => 
        {
            PropertyInfo sourceProp = typeof(T).GetProperty(name);
            PropertyInfo targetProp = anonymousType.GetProperty(name);
            return Expression.Bind(targetProp, Expression.Property(param, sourceProp));
        }).ToArray();

        // 构建匿名类型初始化表达式并转换为object
        NewExpression newExpr = Expression.MemberInit(Expression.New(anonymousType.GetConstructors()[0]), bindings);
        UnaryExpression convertExpr = Expression.Convert(newExpr, typeof(object));

        return Expression.Lambda<Func<T, object>>(convertExpr, param);
    }

    private static Type CreateAnonymousType(List<PropertyInfo> properties)
    {
        // 动态生成类似匿名类型的密封类
        var assemblyName = new AssemblyName($"DynamicAnon_{Guid.NewGuid()}");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
        
        // 类型命名模仿C#匿名类型规则
        var typeName = $"<>f__AnonymousType{properties.Count}_{Guid.NewGuid():N}";
        var typeBuilder = moduleBuilder.DefineType(typeName, 
            TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class | TypeAttributes.Serializable);

        var ctorParamTypes = new List<Type>();
        foreach (var prop in properties)
        {
            // 创建私有只读字段
            string fieldName = $"<{prop.Name}>i__Field";
            var fieldBuilder = typeBuilder.DefineField(fieldName, prop.PropertyType, FieldAttributes.Private | FieldAttributes.InitOnly);
            
            // 创建公共getter属性
            var propBuilder = typeBuilder.DefineProperty(prop.Name, PropertyAttributes.None, prop.PropertyType, null);
            var getterBuilder = typeBuilder.DefineMethod($"get_{prop.Name}", 
                MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, 
                prop.PropertyType, Type.EmptyTypes);
            
            var getterIl = getterBuilder.GetILGenerator();
            getterIl.Emit(OpCodes.Ldarg_0);
            getterIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getterIl.Emit(OpCodes.Ret);
            propBuilder.SetGetMethod(getterBuilder);

            ctorParamTypes.Add(prop.PropertyType);
        }

        // 创建匹配的构造函数
        var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, ctorParamTypes.ToArray());
        var ctorIl = ctorBuilder.GetILGenerator();
        for (int i = 0; i < properties.Count; i++)
        {
            ctorIl.Emit(OpCodes.Ldarg_0);
            ctorIl.Emit(OpCodes.Ldarg, i + 1);
            var field = typeBuilder.GetField($"<{properties[i].Name}>i__Field");
            ctorIl.Emit(OpCodes.Stfld, field);
        }
        ctorIl.Emit(OpCodes.Ret);

        return typeBuilder.CreateType();
    }
}

关键说明

  1. 类型缓存:通过属性列表和类型T生成唯一缓存键,避免重复创建动态类型,提升性能。
  2. 动态类型构建:使用TypeBuilder生成和C#匿名类型结构一致的密封类,包含私有只读字段、公共getter属性和匹配构造函数。
  3. 表达式构建:通过Expression.MemberInit将属性访问结果绑定到动态类型的属性上,最后转换为object类型满足Func<T, object>的返回要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 11:23:19