如何让C#动态构建的Lambda表达式与匿名类型表达式输出一致?
动态构建表达式树生成编译器风格匿名类型实例化的解决方案
要让动态构建的Expression<Func<TestTable, object>>生成和手动编写一致的、带编译器自动命名(如<>f__AnonymousType02`)的匿名类型实例化表达式,核心是要引用编译器实际生成的匿名类型,或者动态生成同格式的密封类型,而不是仅构建语法层面的匿名类型初始化节点。以下是两种可行方案:
方案一:复用编译器生成的匿名类型(简单高效)
先手动创建一个与目标字段列表匹配的匿名类型实例,获取其真实类型,再基于该类型构建表达式树:
// 1. 创建匹配字段的匿名类型实例,获取编译器生成的真实类型 var sampleAnonymous = new { KeyID = default(int), KeyID2 = default(int) }; Type anonymousType = sampleAnonymous.GetType(); // 2. 构建参数表达式(对应lambda的输入参数e) ParameterExpression param = Expression.Parameter(typeof(TestTable), "e"); // 3. 获取匿名类型的构造函数和属性成员 ConstructorInfo ctor = anonymousType.GetConstructors().First(); PropertyInfo[] props = anonymousType.GetProperties(); // 4. 构建属性绑定:将TestTable的属性映射到匿名类型的属性 MemberBinding[] bindings = props.Select(prop => Expression.Bind(prop, Expression.Property(param, prop.Name)) ).ToArray(); // 5. 创建匿名类型实例化表达式,注意要传入构造函数、属性绑定和属性列表 NewExpression newExpr = Expression.New(ctor, bindings.Select(b => ((MemberAssignment)b).Expression), props); // 6. 构建最终的lambda表达式,添加装箱转换(因为返回值是object) Expression<Func<TestTable, object>> lambda = Expression.Lambda<Func<TestTable, object>>( Expression.Convert(newExpr, typeof(object)), param );
此方案直接复用了C#编译器自动生成的<>f__AnonymousTypeXN`类型,生成的表达式与手动编写的示例1完全一致。
方案二:动态生成同格式的匿名类型(完全动态场景)
如果无法提前构造匿名类型实例(字段列表完全动态),可以通过Reflection.Emit动态生成符合编译器命名规范的密封类,再基于该类构建表达式树:
using System.Reflection; using System.Reflection.Emit; // 定义动态字段列表(key是字段名,value是字段类型) var dynamicFields = new Dictionary<string, Type> { { "KeyID", typeof(int) }, { "KeyID2", typeof(int) } }; // 1. 创建动态程序集与模块 var assemblyName = new AssemblyName("DynamicAnonymousTypes"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); // 2. 生成编译器风格的匿名类型名称:<>f__AnonymousType{序号}`{泛型参数数} int typeSequence = 0; // 可根据需要递增避免重复 int propertyCount = dynamicFields.Count; string typeName = $"<>f__AnonymousType{typeSequence}`{propertyCount}"; // 3. 定义密封类(与编译器生成的匿名类型属性一致:public sealed) TypeBuilder typeBuilder = moduleBuilder.DefineType( typeName, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit ); // 4. 为每个字段生成私有字段+公共属性 List<Type> ctorParamTypes = new(); List<PropertyBuilder> propBuilders = new(); foreach (var (propName, propType) in dynamicFields) { // 定义私有字段(编译器生成的匿名类型字段格式为<属性名>i__Field) FieldBuilder fieldBuilder = typeBuilder.DefineField($"<{propName}>i__Field", propType, FieldAttributes.Private); // 定义公共属性 PropertyBuilder propBuilder = typeBuilder.DefineProperty(propName, PropertyAttributes.None, propType, null); // 生成属性的get方法 MethodBuilder getMethod = typeBuilder.DefineMethod( $"get_{propName}", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propType, Type.EmptyTypes ); ILGenerator getIl = getMethod.GetILGenerator(); getIl.Emit(OpCodes.Ldarg_0); getIl.Emit(OpCodes.Ldfld, fieldBuilder); getIl.Emit(OpCodes.Ret); propBuilder.SetGetMethod(getMethod); propBuilders.Add(propBuilder); ctorParamTypes.Add(propType); } // 5. 生成构造函数,为私有字段赋值 ConstructorBuilder ctorBuilder = typeBuilder.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, ctorParamTypes.ToArray() ); ILGenerator ctorIl = ctorBuilder.GetILGenerator(); for (int i = 0; i < dynamicFields.Count; i++) { ctorIl.Emit(OpCodes.Ldarg_0); ctorIl.Emit(OpCodes.Ldarg, i + 1); ctorIl.Emit(OpCodes.Stfld, typeBuilder.GetField($"<{dynamicFields.Keys.ElementAt(i)}>i__Field")); } ctorIl.Emit(OpCodes.Ret); // 6. 完成动态类型创建 Type dynamicAnonymousType = typeBuilder.CreateType(); // 7. 基于动态类型构建表达式树 ParameterExpression param = Expression.Parameter(typeof(TestTable), "e"); ConstructorInfo ctor = dynamicAnonymousType.GetConstructors().First(); var ctorArgs = dynamicFields.Select(kv => Expression.Property(param, kv.Key)).ToArray(); PropertyInfo[] props = dynamicAnonymousType.GetProperties(); NewExpression newExpr = Expression.New(ctor, ctorArgs, props); Expression<Func<TestTable, object>> lambda = Expression.Lambda<Func<TestTable, object>>( Expression.Convert(newExpr, typeof(object)), param );
此方案生成的类型完全模拟编译器的匿名类型格式,表达式输出与示例1一致,适合字段列表完全动态的场景。
内容的提问来源于stack exchange,提问作者Jeremy Miedreich
相关产品推荐
相关产品推荐

