convertire un .net Func <T> in un'espressione .net <Func <T>>


118

Passare da un lambda a un'espressione è facile usando una chiamata al metodo ...

public void GimmeExpression(Expression<Func<T>> expression)
{
    ((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}

public void SomewhereElse()
{
    GimmeExpression(() => thing.DoStuff());
}

Ma vorrei trasformare il Func in un'espressione, solo in rari casi ...

public void ContainTheDanger(Func<T> dangerousCall)
{
    try 
    {
        dangerousCall();
    }
    catch (Exception e)
    {
        // This next line does not work...
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

La riga che non funziona mi dà l'errore in fase di compilazione Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'. Un cast esplicito non risolve la situazione. C'è una struttura per fare questo che sto trascurando?


Non vedo davvero molto utile l'esempio del "caso raro". Il chiamante sta passando il Func <T>. Non è necessario ripetere al chiamante quello che era Func <T> (tramite l'eccezione).
Adam Ralph

2
L'eccezione non viene gestita nel chiamante. Inoltre, poiché sono presenti più siti di chiamata che passano in Func <T> diversi, la cattura dell'eccezione nel chiamante crea una duplicazione.
Dave Cameron

1
L'analisi dello stack delle eccezioni è progettata per mostrare queste informazioni. Se l'eccezione viene generata all'interno dell'invocazione di Func <T>, verrà visualizzata nella traccia dello stack. Per inciso, se dovessi scegliere di andare dall'altra parte, cioè accettare un'espressione e compilarla per l'invocazione, lo perderesti poiché la traccia dello stack mostrerebbe qualcosa di simile at lambda_method(Closure )per l'invocazione del delegato compilato.
Adam Ralph

Immagino che dovresti guardare la risposta in questo [link] [1] [1]: stackoverflow.com/questions/9377635/create-expression-from-func/…
Ibrahim Kais Ibrahim

Risposte:


104

Ooh, non è affatto facile. Func<T>rappresenta un generico delegatee non un'espressione. Se esiste un modo per farlo (a causa delle ottimizzazioni e di altre cose fatte dal compilatore, alcuni dati potrebbero essere buttati via, quindi potrebbe essere impossibile recuperare l'espressione originale), sarebbe disassemblare l'IL al volo e inferire l'espressione (che non è affatto facile). Trattare le espressioni lambda come data ( Expression<Func<T>>) è una magia eseguita dal compilatore (fondamentalmente il compilatore costruisce un albero delle espressioni nel codice invece di compilarlo in IL).

Fatto correlato

Questo è il motivo per cui i linguaggi che spingono i lambda all'estremo (come Lisp) sono spesso più facili da implementare come interpreti . In quei linguaggi, codice e dati sono essenzialmente la stessa cosa (anche in fase di esecuzione ), ma il nostro chip non può capire quella forma di codice, quindi dobbiamo emulare una macchina del genere costruendovi sopra un interprete che la capisca (il scelta fatta dal Lisp come i linguaggi) o sacrificando la potenza (il codice non sarà più esattamente uguale ai dati) in una certa misura (la scelta fatta da C #). In C #, il compilatore offre l'illusione di trattare il codice come dati, consentendo l'interpretazione di lambda come code ( Func<T>) e data ( Expression<Func<T>>) in fase di compilazione .


3
Lisp non deve essere interpretato, può essere facilmente compilato. Le macro dovrebbero essere espanse in fase di compilazione e, se si desidera supportare, evalè necessario avviare il compilatore, ma a parte questo, non ci sono problemi a farlo.
configuratore

2
"Expression <Func <T>> DangerousExpression = () => dangerousCall ();" non è facile?
mheyman

10
@mheyman Ciò creerebbe nuove Expressioninformazioni sulla tua azione wrapper, ma non avrebbe informazioni sull'albero delle espressioni sugli interni del dangerousCalldelegato.
Nenad

34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 

1
Volevo attraversare l'albero della sintassi dell'espressione restituita. Questo approccio mi permetterebbe di farlo?
Dave Cameron

6
@DaveCameron - No. Vedi le risposte sopra - il già compilato Funcsarà nascosto in una nuova espressione. Questo aggiunge semplicemente uno strato di dati sul codice; potresti attraversare un livello solo per trovare il tuo parametro fsenza ulteriori dettagli, quindi sei esattamente dove hai iniziato.
Jonno

21

Quello che probabilmente dovresti fare è cambiare il metodo. Prendi un'espressione>, compila ed esegui. Se fallisce, hai già l'espressione da esaminare.

public void ContainTheDanger(Expression<Func<T>> dangerousCall)
{
    try 
    {
        dangerousCall().Compile().Invoke();;
    }
    catch (Exception e)
    {
        // This next line does not work...
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

Ovviamente è necessario considerare le implicazioni sulle prestazioni di questo e determinare se è qualcosa che è davvero necessario fare.


7

Puoi andare dall'altra parte tramite il metodo .Compile (), tuttavia, non sono sicuro che sia utile per te:

public void ContainTheDanger<T>(Expression<Func<T>> dangerousCall)
{
    try
    {
        var expr = dangerousCall.Compile();
        expr.Invoke();
    }
    catch (Exception e)
    {
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = ((MethodCallExpression)dangerousCall.Body).Method.Name;
        throw new DangerContainer("Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    var thing = new Thing();
    ContainTheDanger(() => thing.CrossTheStreams());
}

6

Se a volte hai bisogno di un'espressione e talvolta hai bisogno di un delegato, hai 2 opzioni:

  • hanno metodi diversi (1 per ciascuno)
  • accetta sempre la Expression<...>versione e basta .Compile().Invoke(...)se vuoi un delegato. Ovviamente questo ha un costo.

6

NJection.LambdaConverter è una libreria che converte i delegati in espressione

public class Program
{
    private static void Main(string[] args) {
       var lambda = Lambda.TransformMethodTo<Func<string, int>>()
                          .From(() => Parse)
                          .ToLambda();            
    }   

    public static int Parse(string value) {
       return int.Parse(value)
    } 
}

4
 Expression<Func<T>> ToExpression<T>(Func<T> call)
        {
            MethodCallExpression methodCall = call.Target == null
                ? Expression.Call(call.Method)
                : Expression.Call(Expression.Constant(call.Target), call.Method);

            return Expression.Lambda<Func<T>>(methodCall);
        }

Puoi elaborare la parte "questo non funzionerà"? Hai davvero provato a compilarlo ed eseguirlo? O non funziona particolarmente nella tua applicazione?
Dmitry Dzygin

1
FWIW, questo potrebbe non essere l'argomento del ticket principale, ma era quello di cui avevo bisogno. Era la call.Targetparte che mi stava uccidendo. Ha funzionato per anni, poi improvvisamente ha smesso di funzionare e ha iniziato a lamentarsi di un blah blah statico / non statico. Comunque grazie!
Eli Gassert


-1

Modificare

// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;

Per

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();

Servy, è un modo assolutamente legale per ottenere un'espressione. sintassi zucchero per costruirlo attraverso expression.lambda e expression.call. Perché pensi che dovrebbe fallire in fase di esecuzione?
Roman Pokrovskij
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.