Come chiamare un lambda usando gli alberi di espressione LINQ in C # / .NET

.net c# expression-trees lambda linq

Domanda

Voglio usare gli alberi di espressione per creare dinamicamente un metodo per chiamare un lambda. Il seguente codice funziona correttamente per la prima chiamata alla funzione ComposeLambda, ma la seconda chiamata non riesce con il seguente messaggio di errore.

Numero errato di argomenti forniti per la chiamata al metodo "Int32 lambda_method (System.Runtime.CompilerServices.Closure, Int32)"

{
    Func<int, int> innerLambda = i => i + 1;    
    var composedLambda = ComposeLambda(innerLambda);
    Console.WriteLine(composedLambda.DynamicInvoke(0));
    var composedLambda2 = ComposeLambda(composedLambda);
    Console.WriteLine(composedLambda2.DynamicInvoke(0));
}

private static Delegate ComposeLambda(Delegate innerLambda)
{
    Func<int, int> outerLambda = i => i + 2;
    var parameter = Expression.Parameter(typeof (int));
    var callInner = Expression.Call(innerLambda.GetMethodInfo(), parameter);
    var callOuter = Expression.Call(outerLambda.GetMethodInfo(), callInner);
    var composedLambdaType = typeof (Func<,>).MakeGenericType(typeof (int), typeof (int));
    var composedLambdaExpression = Expression.Lambda(composedLambdaType, callOuter, parameter);
    var composedLambda = composedLambdaExpression.Compile();
    return composedLambda;
}

Come posso ottenere e trasmettere questo oggetto di chiusura?

Risposta accettata

Non utilizzare Expression.Call(innerLambda.GetMethodInfo(), ...) , questo è solo un problema. Richiama invece il delegato - non hai problemi con il "metodo" del delegato - non solo perdi l'obiettivo (molto importante nei metodi di istanza), ma stai anche violando la privacy (i metodi anonimi sono interni o privati, ad esempio ).

E in questo caso, non hai passato il parametro di chiusura al metodo :) Questo dovrebbe essere piuttosto ovvio dal messaggio di errore - mostra la firma del metodo effettivo (che include la chiusura).

Se si utilizza Expression.Invoke (come si dovrebbe con i delegati), funziona come previsto:

void Main()
{
    Func<int, int> innerLambda = i => i + 1;    
    var composedLambda = ComposeLambda(innerLambda);
    Console.WriteLine(composedLambda.DynamicInvoke(0));
    var composedLambda2 = ComposeLambda(composedLambda);
    Console.WriteLine(composedLambda2.DynamicInvoke(0));
}

private static Delegate ComposeLambda(Delegate innerLambda)
{
    Func<int, int> outerLambda = i => i + 2;
    var parameter = Expression.Parameter(typeof (int));

    var callInner = Expression.Invoke(Expression.Constant(innerLambda), parameter);
    var callOuter = Expression.Invoke(Expression.Constant(outerLambda), callInner);
    var composedLambdaType = typeof (Func<,>).MakeGenericType(typeof (int), typeof (int));
    var composedLambdaExpression = Expression.Lambda(composedLambdaType, callOuter, parameter);
    var composedLambda = composedLambdaExpression.Compile();
    return composedLambda;
}

Oltre a ciò, se si conosce il tipo di delegato appropriato in fase di compilazione, non utilizzare Delegate . In questo caso, è piuttosto semplice usare Func<int, int> , che è quindi possibile richiamare come composedLambda2(0) , ad esempio:

private static Func<int, int> ComposeLambda(Func<int, int> innerLambda)
{
  Func<int, int> outerLambda = i => i + 2;
  var parameter = Expression.Parameter(typeof (int));

  var callInner = Expression.Invoke(Expression.Constant(innerLambda), parameter);
  var callOuter = Expression.Invoke(Expression.Constant(outerLambda), callInner);

  var composedLambdaExpression = Expression.Lambda<Func<int, int>>(callOuter, parameter);
  return composedLambdaExpression.Compile();
}


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é