2011-10-18 7 views
12

Questo è meglio spiegato usando il codice. Ho una classe generica che ha un metodo che restituisce un intero. Ecco una versione semplice ai fini di spiegare ...Come creare un Expression.Lambda quando un tipo non è noto fino al runtime?

public class Gen<T> 
{ 
    public int DoSomething(T instance) 
    { 
     // Real code does something more interesting! 
     return 1; 
    } 
} 

In fase di esecuzione che utilizzare la reflection per scoprire il tipo di qualcosa e poi si desidera creare un'istanza della mia classe Gen per quella specifica tipologia. Questo è abbastanza facile e fatto come questo ...

Type fieldType = // This is the type I have discovered 
Type genericType = typeof(Gen<>).MakeGenericType(fieldType); 
object genericInstance = Activator.CreateInstance(genericType); 

Ora voglio creare un'espressione che avrà come parametro un'istanza del tipo generico e quindi chiama il metodo DoSomething di quel tipo. Quindi voglio l'espressione di svolgere efficacemente questo ...

int answer = genericInstance.DoSomething(instance); 

... tranne che non ho il 'caso' fino a un certo punto, poi in fase di esecuzione e la genericInstance è il tipo generato come si può vedere sopra. Il mio tentativo di creare la Lambda per questo è la seguente ...

MethodInfo mi = genericType.GetMethod("DoSomething", 
             BindingFlags.Instance | BindingFlags.Public); 

var p1 = Expression.Parameter(genericType, "generic"); 
var p2 = Expression.Parameter(fieldType, "instance"); 

var x = Expression.Lambda<Func<genericType, fieldType, int>> 
      (Expression.Call(p1, mi, p2), 
      new[] { p1, p2 }).Compile(); 

... in modo che in seguito posso chiamare con qualcosa di simile ...

int answer = x(genericInstance, instance); 

Naturalmente, non può fornire Func con i parametri di istanza e quindi non ho idea di come parametrizzare la generazione Lambda. Qualche idea?

risposta

18

penso che si sarebbe solo utilizzare il Expression.Lambda che prende il tipo delegato come un tipo piuttosto che come un generico, e creare il Func al volo, come si sta con Gen<>:

MethodInfo mi = genericType.GetMethod("DoSomething", 
           BindingFlags.Instance | BindingFlags.Public); 

var p1 = Expression.Parameter(genericType, "generic"); 
var p2 = Expression.Parameter(fieldType, "instance"); 
var func = typeof (Func<,,>); 
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int)); 
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2), 
       new[] { p1, p2 }).Compile(); 

Ciò restituirà un Delega piuttosto che uno Func fortemente tipizzato, ma puoi ovviamente lanciarlo se necessario (e apparentemente difficile se non sai a cosa stai trasmettendo), o invocarlo dinamicamente usando DynamicInvoke su di esso.

int answer = (int) x.DynamicInvoke(genericInstance, instance); 

EDIT:

Una buona idea che effettivamente funziona. Sfortunatamente, la ragione per cui voglio usare un Lambda compilato fortemente tipizzato è la performance. L'uso di DynamicInvoke è lento rispetto a un Lambda tipizzato.

Questo sembra funzionare senza necessità di invocazione dinamica.

var p1 = Expression.Parameter(genericType, "generic"); 
var p2 = Expression.Parameter(fieldType, "instance"); 
var func = typeof(Func<,,>); 
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int)); 
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2), new[] { p1, p2 }); 
var invoke = Expression.Invoke(x, Expression.Constant(genericInstance), Expression.Constant(instance)); 
var answer = Expression.Lambda<Func<int>>(invoke).Compile()(); 

EDIT 2:

Una versione molto semplificata:

Type fieldType = ;// This is the type I have discovered 
Type genericType = typeof(Gen<>).MakeGenericType(fieldType); 
object genericInstance = Activator.CreateInstance(genericType); 
MethodInfo mi = genericType.GetMethod("DoSomething", 
           BindingFlags.Instance | BindingFlags.Public); 
var value = Expression.Constant(instance, fieldType); 
var lambda = Expression.Lambda<Func<int>>(Expression.Call(Expression.Constant(genericInstance), mi, value)); 
var answer = lambda.Compile()(); 
+0

Una buona idea che funziona davvero. Sfortunatamente, la ragione per cui voglio usare un Lambda compilato fortemente tipizzato è la performance. L'uso di DynamicInvoke è lento rispetto a un Lambda tipizzato. –

+0

È possibile acquisire variabili in un albero di espressioni? Aiuterei a catturare l'istanza generica in quanto non cambierà mai. –

+0

@PhilWright Hm, vedo. Fammi vedere cos'altro posso inventare. – vcsjones

1

Questa risposta si applica solo se si utilizza .NET 4.0.

Se si effettua genericInstancedynamic invece di object, quindi è possibile chiamare il metodo DoSomething su di esso direttamente, e il runtime linguaggio dinamico si prenderà cura di tutto per voi.

class Type1 { 
    public int DoSomething() { return 1; } 
} 
class Type2 { 
    public int DoSomething() { return 2; } 
} 

static void TestDynamic() { 
    dynamic t1 = Activator.CreateInstance(typeof(Type1)); 
    int answer1 = t1.DoSomething(); // returns 1 

    dynamic t2 = Activator.CreateInstance(typeof(Type2)); 
    int answer2 = t2.DoSomething(); // returns 2 
} 

Se è necessario mantenere questa struttura di classe (Gen<T>), quindi non vedo un modo semplice intorno al fatto che non si conosce il tipo di T in fase di compilazione. Se si desidera chiamare il delegato, è necessario conoscerne l'intero tipo in fase di compilazione oppure è necessario passare i parametri come oggetti.

L'utilizzo di dynamic consente di nascondere la complessità di ottenere il MethodInfo, ecc. E offre prestazioni eccellenti. L'unico svantaggio rispetto a DynamicInvoke che vedo è che ritengo che abbiate il sovraccarico iniziale di risoluzione della chiamata dinamica una volta per ogni sito di chiamata. I binding vengono memorizzati nella cache in modo che vengano eseguiti molto rapidamente dal secondo momento se lo si chiama su oggetti dello stesso tipo.

0

È meglio accettare uno object e utilizzare convert in un tipo noto.

Ecco un esempio, come costruire l'accesso a una proprietà per nome dalla profondità sconosciuta:

var model = new { A = new { B = 10L } }; 
string prop = "A.B"; 
var parameter = Expression.Parameter(typeof(object)); 
Func<object, long> expr = (Func<object, long>) Expression.Lambda(prop.Split('.').Aggregate<string, Expression>(Expression.Convert(parameter, model.GetType()), Expression.Property), parameter).Compile(); 
expr(model).Dump(); 

Evita costi aggiuntivi di DynamicInvoke quando il tipo di delegato è sconosciuta al momento della compilazione.

Problemi correlati