2010-09-18 13 views
21

En C# 3.0 puede utilizar expresiones para crear una clase con la siguiente sintaxis:Cómo usar Expression para crear un tipo anónimo?

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

Pero, ¿cómo utilizar expresiones para crear una clase anónima?

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

Posible dup de: http://stackoverflow.com/questions/606104/linq-expression-tree-question –

+1

@Flash, esto no es posible, al menos no directamente. El compilador hace mucha "magia" cuando creas tipos anónimos: es azúcar sintáctica para declarar realmente una clase genuina de C# con un montón de propiedades. El compilador hace todo esto por ti. No existe un tipo de árbol de expresiones que realmente haga todo esto automáticamente. Si miras el enlace al que hice referencia, proporciona una solución alternativa. Sin embargo, usa Reflection.Emit, que no es para el fin del corazón. –

+1

Kirk: El OP quiere * construir * una clase anónima, no * crear * una desde el principio. Siempre que sepa en tiempo de compilación cuáles son los nombres y tipos de las propiedades, puede hacer que el compilador cree el tipo para él y todo lo que tiene que hacer es encontrar la manera de resolverlo. – Gabe

Respuesta

17

estás cerca, pero hay que tener en cuenta que los tipos anónimos no tienen constructores por defecto. Los siguientes código imprime { 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); 

Si no tiene que crear muchos casos de este tipo, Activator.CreateInstance harán igual de bien (que es más rápido para unos pocos casos, pero más lento para muchos). Este código imprime { Name = ghi, Num = 789 }:

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

Pero, escriba anonType = new {Name = "abc", Num = 123} .GetType() ; <- Es un código estático, no un código dinámico. – Flash

+2

@Flash: si tiene la impresión de que el código C# 'new {Name =" abc ", Num = 123}', cuando se utiliza en una expresión LINQ, crea un nuevo tipo en tiempo de ejecución, entonces está equivocado. El compilador crea el tipo en tiempo de compilación y el Árbol de expresiones generado no puede distinguirse de uno que utiliza un tipo no anónimo. – Timwi

+0

Flash: ¿Desea * tipos anónimos * dinámicos? ¿Qué piensas hacer con ellos? – Gabe

6

Desde un tipo anónimo no tiene un constructor por defecto vacío, no se puede utilizar la sobrecarga de Expression.New(Type) ... usted tiene que proporcionar los parámetros ConstructorInfo y al método Expression.New. Para hacer eso, debe poder obtener el tipo ... por lo que debe crear una instancia "stub" del tipo anónimo, y usar eso para obtener el Type y el ConstructorInfo, y luego pasar los parámetros al Expression.New método.

De esta manera:

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(); 
+1

Esta es una solución inteligente. Pero, por lo general, la razón por la que uno necesita escribir algo utilizando árboles de expresión (la API) es precisamente porque uno * no * tiene esta información en tiempo de compilación. Si lo hiciera, habrían usado expresiones ordinarias de C# en primer lugar. –

+0

@Kirk OPs code beg to differ. Y hay muchas situaciones en las que sabría el tipo, pero aún así tenía que crear un ExpressionTree. DynamicLinq-2-Sql para uno –

+0

Sólo nitpicking, los tipos anónimos tienen constructores vacíos si el tipo anónimo es 'new {}' :) – nawfal

3

Puede evitar el uso de DynamicInvoke que es muy lento. Puede utilizar la inferencia de tipo en C# para obtener una instancia genérica de su tipo anónimo. Algo así como:

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

Ahora se puede llamar,

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. 

se puede utilizar el método de AnonymousInstantiator para generar funciones para crear instancias de cualquier tipo anónimo con cualquier número de propiedades, al igual que usted tiene que pasar una adecuada ejemplo primero. Los parámetros de entrada deben pasarse como una matriz de objetos. Si te preocupa el rendimiento del boxeo allí, entonces debes escribir un instanciador personalizado que acepte solo string y int como parámetros de entrada, pero el uso de tal instanciador será un poco más limitado.

Cuestiones relacionadas