如何使用Expression构建匿名类型?

Fla*_*ash 24 c# linq anonymous-types expression-trees

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

var exp = Expression.New(typeof(MyClass));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();
Run Code Online (Sandbox Code Playgroud)

但是如何使用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();
Run Code Online (Sandbox Code Playgroud)

Gab*_*abe 21

你很接近,但你必须要知道匿名类型没有默认构造函数.以下代码打印{ 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);
Run Code Online (Sandbox Code Playgroud)

如果您不必创建此类型的许多实例,那么Activator.CreateInstance也会这样做(对于少数实例来说更快,但对于许多实例来说速度更慢).此代码打印{ Name = ghi, Num = 789 }:

Type anonType = new { Name = "abc", Num = 123 }.GetType();
object myObj = Activator.CreateInstance(anonType, "ghi", 789);
Console.WriteLine(myObj);
Run Code Online (Sandbox Code Playgroud)

  • 我个人更喜欢使用`Type anonType = new {Name = default(string),Num = default(int)} .GetType();`在定义匿名类型时,我发现它更清楚地将定义与使用分开. (5认同)
  • @Flash:如果你的印象是C#代码`new {Name ="abc",Num = 123}`,当在LINQ表达式中使用时,在运行时创建一个新类型,那你就错了.编译器在编译时创建类型,生成的表达式树与使用非匿名类型的表达式树无法区分. (3认同)
  • 但是,输入anonType = new {Name ="abc",Num = 123} .GetType(); < - 它是静态代码,不是动态代码. (2认同)

Ric*_*ein 6

由于匿名类型没有默认的空构造函数,因此无法使用Expression.New(Type)重载...您必须为方法提供ConstructorInfo和参数Expression.New.要做到这一点,你必须能够获得Type ...所以你需要创建一个匿名类型的"存根"实例,并使用它来获取Type,然后ConstructorInfo,然后将参数传递给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();
Run Code Online (Sandbox Code Playgroud)

  • 这是一个聪明的解决方案.但通常需要使用表达式树(API)来编写内容的原因恰恰是因为一个*在编译时没有这个信息.如果有人这样做,他们首先会使用普通的C#表达式. (2认同)

naw*_*fal 6

你可以避免使用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();
}
Run Code Online (Sandbox Code Playgroud)

现在你可以打电话,

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.
Run Code Online (Sandbox Code Playgroud)

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