La compilation d'une expression lambda donne un délégué avec l'argument Closure

c# expression-trees lambda

Question

Lorsque j'utilise Expression.Lambda( ... ).Compile() afin de créer un délégué à partir d'un arbre d'expression, le résultat est un délégué dont le premier argument est Closure .

public static Func<T, T, T> CreateTest<T>()
{
    ParameterExpression a = Expression.Parameter( typeof( T ) );
    ParameterExpression b = Expression.Parameter( typeof( T ) );
    Expression addition = Expression.Add( a, b );

    return (Func<T, T, T>)Expression.Lambda( addition, a, b ).Compile();
}

...

// 'addition' equals
// Int32 lambda_method(
//     System.Runtime.CompilerServices.Closure,
//     Int32,
//     Int32 )
Func<int, int, int> addition = DelegateHelper.CreateTest<int>();
int result = addition( 5, 5 );

Je peux facilement appeler le délégué via un code ordinaire sans passer d'objet Closure , mais d'où vient cette Closure ?

Comment puis-je appeler ce délégué de manière dynamique?

// The following does not work.
// Exception: MethodInfo must be a runtime MethodInfo object.    
MethodInfo additionMethod = addition.Method;
int result = (int)additionMethod.Invoke( null, new object[] { 5, 5 } );

En utilisant des arbres d'expression, il me semble que je dois passer l'objet Closure .

PropertyInfo methodProperty
    = typeof( Delegate ).GetProperty( "Method", typeof( MethodInfo ) );
MemberExpression getDelegateMethod
    = Expression.Property( Expression.Constant( addition ), methodProperty );
Func<MethodInfo> getMethodInfo
    = (Func<MethodInfo>)Expression.Lambda( getDelegateMethod ).Compile();
// Incorrect number of arguments supplied for call to method
// 'Int32 lambda_method(System.Runtime.CompilerServices.Closure, Int32, Int32)'
Expression call
    = Expression.Call(
        getMethodInfo(),
        Expression.Constant( 5 ), Expression.Constant( 5 ) );

C'est un exemple simplifié qui n'a pas de sens en soi. Ce que je cherche réellement à faire est de pouvoir Func<Action<SomeObject>> par exemple, Func<Action<SomeObject>> avec Func<Action<object>> . Je peux déjà le faire pour les délégués non imbriqués. Ceci est utile pendant la réflexion, comme discuté ici .

Comment dois-je initialiser correctement cet objet Closure ou comment l'empêcher de s'y trouver?

Réponse acceptée

Le type de Closure vous voyez est un détail d'implémentation. Le MSDN est assez explicite à ce sujet:

Cette API prend en charge l'infrastructure .NET Framework et n'est pas conçue pour être utilisée directement à partir de votre code. Représente l'état d'exécution d'une méthode générée dynamiquement.

Un arbre d'expression peut avoir un état.

L'instance Closure contiendra toutes les constantes non littérales sur lesquelles se termine l'expression lambda. Il peut également contenir une chaîne de délégués pour les lambdas imbriqués dans les arbres d'expression.

Pour ce faire, le compilateur d’arbres d’expression utilise un petit truc mignon. Il génère en mémoire un code utilisant un DynamicMethod , qui est par définition statique. Pourtant, ils créent un délégué qui est « fermé sur son premier argument» . Cela signifie que le CLR passera le champ cible du délégué en tant que premier argument de la méthode statique, de sorte que vous n'ayez pas à le faire. Cacher efficacement l'argument de clôture de votre part.

La solution à votre problème est simple, n'essayez pas d'appeler la méthode, appelez le délégué, en utilisant Delegate.DynamicInvoke lorsque vous utilisez la réflexion ou Expression.Invoke dans le contexte d'un arbre d'expression.



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