2010-09-18 12 views
21

En C# 3.0, vous pouvez utiliser l'expression pour créer une classe avec la syntaxe suivante:Comment utiliser Expression pour créer un type anonyme?

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

Mais comment utilisez-vous Expression pour créer une classe anonyme?

//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

Dup possible de: http://stackoverflow.com/questions/606104/linq-expression-tree-question –

+1

@Flash, ce n'est pas possible, du moins pas directement. Le compilateur fait beaucoup de "magie" lorsque vous créez des types anonymes - c'est du sucre syntaxique pour déclarer une véritable classe C# avec un tas de propriétés. Le compilateur fait tout cela pour vous. Il n'y a pas de type d'arbre d'expression qui fait tout cela automatiquement pour vous. Si vous regardez le lien que j'ai référencé, il fournit une solution de contournement. Cependant, il utilise Reflection.Emit, ce qui n'est pas pour la feinte du coeur. –

+1

Kirk: L'OP veut * construire * une classe anonyme, pas * créer * une à partir de zéro. Tant qu'il sait au moment de la compilation quels sont les noms et les types des propriétés, il peut demander au compilateur de créer le type pour lui et tout ce qu'il a à faire est de savoir comment l'utiliser. – Gabe

Répondre

17

Vous êtes proche, mais vous devez savoir que les types anonymes ne sont pas des constructeurs par défaut. Les impressions de code suivant: { 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 vous ne disposez pas de créer de nombreux cas de ce type, Activator.CreateInstance fera tout aussi bien (il est pour beaucoup plus rapide pour quelques cas, mais plus lent). Ce code imprime { Name = ghi, Num = 789 }:

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

Mais, tapez anonType = new {Name = "abc", Num = 123} .GetType() ; <- C'est un code statique, pas un code dynamique. – Flash

+2

@Flash: Si vous avez l'impression que le code C# 'new {Nom =" abc ", Num = 123}', utilisé dans une expression LINQ, crée un nouveau type au moment de l'exécution, alors vous vous trompez. Le compilateur crée le type lors de la compilation, et l'arbre d'expression généré est indiscernable de celui qui utilise un type non anonyme. – Timwi

+0

Flash: Vous voulez des types * dynamiques anonymes *? Qu'avez-vous l'intention de faire avec eux? – Gabe

6

Comme un type anonyme ne dispose pas d'un défaut constructeur vide, vous ne pouvez pas utiliser la surcharge Expression.New(Type) ... vous devez fournir les ConstructorInfo et les paramètres à la méthode Expression.New. Pour ce faire, vous devez être en mesure d'obtenir le type ... donc vous devez créer une instance "stub" du type anonyme, et l'utiliser pour obtenir le Type, et le ConstructorInfo, puis passer les paramètres à la Expression.New méthode.

Comme ceci:

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

Ceci est une solution intelligente. Mais généralement, la raison pour laquelle il faut écrire quelque chose en utilisant des arbres d'expression (l'API) est précisément parce que l'on n'a * * pas cette information à la compilation. Si tel était le cas, ils auraient utilisé des expressions C# ordinaires en premier lieu. –

+0

@Kirk OPs code commencer à différer. Et il y a beaucoup de situations où vous connaissiez le type mais vous deviez construire un ExpressionTree. DynamicLinq-2-Sql pour un –

+0

Just nitpicking, les types anonymes ont des constructeurs vides si le type anonyme est 'new {}' :) – nawfal

3

Vous pouvez éviter d'utiliser DynamicInvoke qui est douloureusement lent. Vous pouvez utiliser l'inférence de type en C# pour obtenir une instanciation générique de votre type anonyme. Quelque chose comme:

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

Maintenant, vous pouvez appeler,

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. 

Vous pouvez utiliser la méthode AnonymousInstantiator pour générer des fonctions pour instancier tout type anonyme avec un certain nombre de propriétés, juste que vous devez passer un approprié exemple en premier. Les paramètres d'entrée doivent être transmis en tant que tableau d'objets. Si vous vous inquiétez des performances de boxe, alors vous devez écrire un instantiateur personnalisé qui accepte seulement string et int comme paramètres d'entrée, mais l'utilisation d'un tel instanciant sera un peu plus limitée.

Questions connexes