Comment appeler un lambda à l'aide d'arbres d'expression LINQ en C # / .NET

.net c# expression-trees lambda linq

Question

Je veux utiliser des arbres d'expression pour créer dynamiquement une méthode pour appeler un lambda. Le code suivant fonctionne correctement pour le premier appel à la fonction ComposeLambda, mais le deuxième appel échoue avec le message d'erreur suivant.

Nombre incorrect d'arguments fournis pour l'appel de la méthode '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;
}

Comment puis-je obtenir et transmettre cet objet de fermeture?

Réponse acceptée

N'utilisez pas Expression.Call(innerLambda.GetMethodInfo(), ...) , c'est simplement poser des problèmes. Invoquez le délégué à la place - vous n'avez rien à faire avec la "méthode" du délégué - non seulement vous perdez la cible (très important dans les méthodes d'instance), mais vous violez également la confidentialité (les méthodes anonymes sont internes ou privées, par exemple )

Et dans ce cas, vous n'avez pas passé le paramètre de fermeture à la méthode :) Cela devrait être assez évident à partir du message d'erreur - il vous montre la signature de la méthode réelle (qui inclut la fermeture).

Si vous utilisez Expression.Invoke (comme vous devriez le faire avec des délégués), cela fonctionnera comme prévu:

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

De plus, si vous connaissez le type de délégué approprié au moment de la compilation, n'utilisez pas Delegate . Dans ce cas, il est assez simple d’utiliser Func<int, int> , que vous pouvez ensuite appeler en tant que composedLambda2(0) , par exemple:

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


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi