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

32
在C# 3.0中,您可以使用表达式(Expression)来创建一个具有以下语法的类:
var exp = Expression.New(typeof(MyClass));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();

但是如何使用表达式来创建匿名类?

//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();

1
@Flash,这是不可能的,至少不能直接实现。编译器在创建匿名类型时为您完成了许多“魔法”——它是一种语法糖,实际上声明了一个真正的C#类及其所有属性。编译器只是替您完成了所有这些操作。实际上并没有表达式树类型能够自动完成这一切。如果您查看我引用的链接,它提供了一种解决方法。但是,它使用的是 Reflection.Emit,这并不适合新手。 - Kirk Woll
1
Kirk: OP想要构建一个匿名类,而不是从头开始创建一个类。只要在编译时知道属性的名称和类型,他就可以让编译器为他创建类型,他所要做的就是弄清如何实例化它。 - Gabe
1
@Gabe,我不同意你对OP想要什么的解释,但我想我们会看到的。;) - Kirk Woll
Kirk:根据OP提供的var exp = Expression.New(typeof(MyClass));示例,似乎他所谓的“创建一个类”实际上是“创建一个现有类的实例”。如果他想要创建一个新类,那么它不可能是匿名的,这是没有意义的。 - Gabe
1
@Gabe,我不同意。他注释掉了类的定义,可能是为了找到使用表达式树来实现这一点的方法。此外,这篇文章的标题是“如何使用表达式构建匿名类型?”我从未使用“构建”这个动词来指代“实例化”。 - Kirk Woll
显示剩余3条评论
3个回答

26

你离正确答案很近了,但你需要注意匿名类型没有默认构造函数。以下代码将打印出{ 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);

6
但是,Type anonType = new { Name = "abc", Num = 123 }.GetType(); <-- 这是静态代码,不是动态代码。 - Flash
3
如果你认为在 LINQ 表达式中使用 C# 代码 new { Name = "abc", Num = 123 } 会在运行时创建一个新类型,那么你是错误的。编译器会在编译时创建该类型,并生成的表达式树与使用非匿名类型的表达式树无法区分。 - Timwi
5
我个人更喜欢使用 Type anonType = new { Name = default(string), Num = default(int) }.GetType(); 来定义匿名类型,因为我认为这样可以更清晰地区分定义和使用。 - Lukazoid
@ghord typeof(T).GetType() 总是会返回 typeof(System.Type),在我看来这样相当无用 ;) - Mike Marynowski
我认为使用Activator.CreateInstance的第二种方法比使用DynamicInvoke进行动态函数调用要快得多。 - nawfal
显示剩余6条评论

7

您可以避免使用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作为输入参数,但是这种实例化程序的使用将会更加有限。

6

由于匿名类型没有默认的空构造函数,因此您无法使用Expression.New(Type)重载...您必须提供ConstructorInfo和参数以在Expression.New方法中使用。为此,您需要能够获取类型...所以您需要制作一个匿名类型的“存根”实例,并使用该实例获取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();

6
这是一个聪明的解决方案。但通常需要使用表达式树(API)编写某些内容的原因恰恰在于编译时没有这些信息。如果有的话,他们本来就会首先使用普通的C#表达式。 - Kirk Woll
@Kirk的代码与此不同。有很多情况下,您可能会知道类型但仍需要构建ExpressionTree。例如DynamicLinq-2-Sql。 - Rune FS
只是挑刺一下,如果匿名类型是 new { },则匿名类型具有空构造函数 :) - nawfal
1
@KirkWoll 实际上,通常情况下你在编译时就已经知道类型信息了,你的代码可以利用这些信息来构建和编译一些复杂的表达式树,如果手动编写和维护将会是一场噩梦。当你需要以新颖的配置进行数十次或数百次相同的复杂操作时,它们可能是简单性/表现力和性能之间的最佳折衷方案。 - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接