La compilazione di un'espressione lambda risulta in delegato con argomento Closure

c# expression-trees lambda

Domanda

Quando uso Expression.Lambda( ... ).Compile() per creare un delegato da un albero di espressioni, il risultato è un delegato di cui il primo argomento è 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 );

Posso chiamare facilmente il delegato tramite codice ordinario senza passare un oggetto Closure , ma da dove viene questa Closure ?

Come posso chiamare questo delegato in modo dinamico?

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

Utilizzando gli alberi di espressione sembra che debba passare l'oggetto 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 ) );

Questo è un esempio semplificato che non ha senso di per sé. Quello che in realtà sto cercando di ottenere è di essere in grado di racchiudere, ad esempio, Func<Action<SomeObject>> con Func<Action<object>> . Posso già farlo per i delegati non annidati. Questo è utile durante la riflessione, come discusso qui .

Come dovrei inizializzare correttamente questo oggetto Closure , o come posso evitare che sia lì?

Risposta accettata

Il tipo di Closure che vedi è un dettaglio di implementazione. MSDN è piuttosto esplicito a riguardo:

Questa API supporta l'infrastruttura .NET Framework e non è progettata per essere utilizzata direttamente dal codice. Rappresenta lo stato di esecuzione di un metodo generato dinamicamente.

Un albero di espressioni può avere uno stato.

L'istanza di chiusura conterrà tutte le costanti non letterali che l'espressione lambda, beh, si chiude. Può anche contenere una catena di delegati per lambda annidate negli alberi di espressione.

Per ottenere ciò, il compilatore di espressioni tree usa un piccolo trucco carino. Genera nel codice di memoria usando un DynamicMethod , che è per definizione statico. Tuttavia, stanno creando un delegato che è "chiuso sul suo primo argomento" . Significa che il CLR passerà il campo obiettivo del delegato come primo argomento del metodo statico, quindi non è necessario. Nascondere efficacemente l'argomento Closure da te.

La soluzione al problema è semplice, non provare a chiamare il metodo, richiamare il delegato, utilizzando Delegate.DynamicInvoke quando si utilizza reflection o Expression.Invoke nel contesto di un albero di espressioni.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow