Come creare un Expression.Lambda quando un tipo non è noto fino al runtime?

c# expression-trees generics lambda

Domanda

Questo è meglio spiegato usando il codice. Ho una classe generica che ha un metodo che restituisce un intero. Ecco una versione semplice allo scopo di spiegare ...

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

Durante il runtime utilizzo la reflection per scoprire il tipo di qualcosa e poi voglio creare un'istanza della mia classe Gen per quel tipo specifico. Questo è abbastanza facile e fatto in questo modo ...

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 prenderà come parametro un'istanza del tipo generico e quindi chiamerà il metodo DoSomething di quel tipo. Quindi voglio che l'espressione esegua efficacemente questo ...

int answer = genericInstance.DoSomething(instance);

... eccetto che non ho l'istanza fino a un certo punto in fase di esecuzione e l'istanza generica è il tipo generato come si può vedere sopra. Il mio tentativo di creare il Lambda per questo è il 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();

... così che in seguito potrò chiamarlo con qualcosa di simile ...

int answer = x(genericInstance, instance);

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

Risposta accettata

Penso che useresti semplicemente Expression.Lambda che prende il tipo di delegato come tipo piuttosto che come generico e crea il tuo Func al volo come fai 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 Delegato piuttosto che un Func fortemente tipizzato, ma puoi ovviamente lanciarlo se necessario (e apparentemente difficile se non sai a cosa stai lanciando), o invocarlo dinamicamente usando DynamicInvoke su di esso.

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

MODIFICA :

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.

Questo sembra funzionare senza bisogno di un richiamo dinamico.

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

MODIFICA 2 :

Una versione notevolmente 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()();

Risposta popolare

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

Se si crea genericInstance dynamic anziché object , è possibile chiamare direttamente il metodo DoSomething su di esso e il runtime della lingua dinamica si prenderà cura di tutto per te.

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> ), allora non vedo un modo semplice per non conoscere il tipo 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 ti consente di nascondere la complessità di ottenere MethodInfo , ecc. E offre prestazioni eccellenti. L'unico svantaggio rispetto a DynamicInvoke che vedo è che ritengo che tu abbia il sovraccarico iniziale di risolvere la 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.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché