如何使用表达式构建匿名类型?

Posted

技术标签:

【中文标题】如何使用表达式构建匿名类型?【英文标题】:How to use Expression to build an Anonymous Type? 【发布时间】:2011-04-14 00:32:01 【问题描述】:

在 C# 3.0 中,您可以使用 Expression 创建具有以下语法的类:

var exp = Expression.New(typeof(MyClass));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();

但是如何使用 Expression 来创建 Anonymous 类?

//anonymousType = typeof(new Name="abc", Num=123);
Type anonymousType = Expression.NewAnonymousType???  <--How to do ?
var exp = Expression.New(anonymousType);
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();

【问题讨论】:

可能重复:***.com/questions/606104/… @Flash,这是不可能的,至少不是直接的。当您创建匿名类型时,编译器为您做了很多“魔术”——它是实际声明具有一堆属性的真正 C# 类的语法糖。编译器只是为你做这一切。没有表达式树类型可以自动为您完成所有这些工作。如果您查看我引用的链接,它提供了一种解决方法。但是,它使用了 Reflection.Emit,它不适合虚心。 Kirk:OP 想要构建一个匿名类,而不是从头开始创建一个。只要他在编译时知道属性的名称和类型是什么,他就可以让编译器为他创建类型,他所要做的就是弄清楚如何实例化它。 @Gabe,我不同意你对 OP 想要什么的解释,但我想我们会看到的。 ;) @Gabe,我不同意。他注释掉了他对类的定义,大概是为了找到一种用表达式树来做到这一点的方法。此外,这篇文章的标题是“”我从来没有用动词“构建”来指代“实例化”。 【参考方案1】:

你很接近,但你必须知道匿名类型没有默认构造函数。以下代码打印 Name = def, Num = 456

Type anonType = new  Name = "abc", Num = 123 .GetType();
var exp = Expression.New(
            anonType.GetConstructor(new[]  typeof(string), typeof(int) ),
            Expression.Constant("def"),
            Expression.Constant(456));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();
Console.WriteLine(myObj);

如果您不必创建许多这种类型的实例,Activator.CreateInstance 也可以做到(少数实例更快,但许多实例更慢)。此代码打印 Name = ghi, Num = 789 :

Type anonType = new  Name = "abc", Num = 123 .GetType();
object myObj = Activator.CreateInstance(anonType, "ghi", 789);
Console.WriteLine(myObj);

【讨论】:

但是,类型 anonType = new Name = "abc", Num = 123 .GetType(); @Flash:如果您的印象是 C# 代码 new Name = "abc", Num = 123 在 LINQ 表达式中使用时会在运行时创建一个新类型,那么您就错了。编译器在编译时创建类型,生成的表达式树与使用非匿名类型的表达式树没有区别。 Flash:您想要动态匿名类型吗?你打算用它们做什么? 我个人更喜欢在定义匿名类型时使用Type anonType = new Name = default(string), Num = default(int) .GetType();,因为我发现这样可以更清楚地将定义与使用分开。 通常,您会使包含此代码的方法成为通用方法,这样 T 类型的参数之一就是匿名对象。然后,您可以在方法主体中使用 typeof(T).GetType() 而不是内联 new ...【参考方案2】:

您可以避免使用速度非常慢的DynamicInvoke。您可以使用 C# 中的类型推断来通用实例化您的匿名类型。比如:

public static Func<object[], T> AnonymousInstantiator<T>(T example)

    var ctor = typeof(T).GetConstructors().First();
    var paramExpr = Expression.Parameter(typeof(object[]));
    return Expression.Lambda<Func<object[], T>>
    (
        Expression.New
        (
            ctor,
            ctor.GetParameters().Select
            (
                (x, i) => Expression.Convert
                (
                    Expression.ArrayIndex(paramExpr, Expression.Constant(i)),
                    x.ParameterType
                )
            )
        ), paramExpr).Compile();

现在你可以打电话了,

var instantiator = AnonymousInstantiator(new  Name = default(string), Num = default(int) );

var a1 = instantiator(new object[]  "abc", 123 ); // strongly typed
var a2 = instantiator(new object[]  "xyz", 789 ); // strongly typed
// etc.

您可以使用AnonymousInstantiator 方法生成函数来实例化具有任意数量属性的任何匿名类型,只是您必须首先传递一个适当的示例。输入参数必须作为对象数组传递。如果您担心那里的拳击性能,那么您必须编写一个自定义实例化器,它只接受stringint 作为输入参数,但是这样的实例化器的使用会受到更多限制。

【讨论】:

【参考方案3】:

由于匿名类型没有默认的空构造函数,您不能使用Expression.New(Type) 重载...您必须为Expression.New 方法提供ConstructorInfo 和参数。为此,您必须能够获取 Type ... 所以您需要创建匿名类型的“存根”实例,并使用它来获取 TypeConstructorInfo,然后通过Expression.New 方法的参数。

像这样:

var exp = Expression.New(new  Name = "", Num = 0 .GetType().GetConstructors()[0], 
                         Expression.Constant("abc", typeof(string)), 
                         Expression.Constant(123, typeof(int)));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();

【讨论】:

这是一个聪明的解决方案。但通常需要使用表达式树(API)编写某些东西的原因正是因为一个没有在编译时拥有此信息。如果有人这样做,他们一开始就会使用普通的 C# 表达式。 @Kirk OPs 代码要求不同。并且在很多情况下您知道类型但仍然必须构建 ExpressionTree。 DynamicLinq-2-Sql 一个 只是吹毛求疵,如果匿名类型是new ,匿名类型的构造函数是空的:) @KirkWoll 实际上,关键是您确实在编译时知道类型信息,并且您的代码可以使用这些信息来构造和编译一些非常精细的表达式树手工编码和维护将是一场噩梦。当您需要在新颖的配置中执行数十或数百次相同类型的复杂事情时,它们可能是简单性/表现力和性能之间的最佳折衷。

以上是关于如何使用表达式构建匿名类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 lambda 表达式和匿名类型获取类型的属性名称?

如何使用 Lambda 或 Linq 将匿名类型转换为原始类型成员

如何创建 LINQ 表达式树以选择匿名类型

Kotlin函数 ⑤ ( 匿名函数变量类型推断 | 匿名函数参数类型自动推断 | 匿名函数又称为 Lambda 表达式 )

如何使用匿名方法返回值?

中等信任的匿名类型,使用反射而不是表达式