2011-11-26 16 views
22

Supponiamo che io sono una classe:Perché gli argomenti di espressione lambda sono ambigui tra Func ed Expression <Func>?

class MyClass { 
    public int MyMethod(Func<int, int> f) { return 0; } 
    public int MyMethod(Expression<Func<int, int>> f) { return 1; } 
} 

Quando provo a chiamare il metodo con un'espressione lambda, ottengo un errore di compilazione che indica che la chiamata è ambiguo tra i due overload:

var myClass = new MyClass(); 
myClass.MyMethod(x => 1 + x); // Error! 

mentre, ovviamente, chiamando con un tipo esplicito funziona bene:

myClass.MyMethod((Func<int, int>)(x => 1 + x)); // OK, returns 0 
myClass.MyMethod((Expression<Func<int, int>>)(x => 1 + x)); // OK, returns 1 

l'albero di espressione contiene ulteriori informazioni (il codice vero e proprio), e posso wa nt per utilizzare queste informazioni quando sono disponibili. Ma voglio anche che il mio codice funzioni con i delegati. Sfortunatamente, questa ambiguità lo rende così devo trovare un altro modo per distinguere tra le due chiamate, che incasina un'API altrimenti pulita.

La specifica C# non dice nulla su questa situazione specifica, quindi in questo senso il comportamento corrisponde alle specifiche.

Tuttavia, è necessario specificare che l'albero delle espressioni deve essere preferito al delegato. Il metodo Compile funge da conversione esplicita da un albero di espressioni a un delegato. L'albero delle espressioni contiene più informazioni e quando si compila un delegato, si perdono tali informazioni. Non c'è conversione nell'altra direzione.

Ci sono motivi per non preferire l'albero delle espressioni?

+3

L'argomento a favore di 'Func' è che è possibile eseguirlo direttamente senza dover passare attraverso una fase di compilazione relativamente costosa. – Lee

risposta

6

Rispondendo alla domanda sul titolo, sono ambigui perché il sistema di tipi non ha il concetto di "espressione lambda". È una funzionalità del compilatore che può essere convertita in un delegato o in un albero di espressioni, quindi è necessario essere espliciti con quale tipo si desidera convertirlo. La maggior parte delle volte il target viene anche inferito automaticamente dal compilatore a causa del contesto in cui viene utilizzata l'espressione lambda. Ad esempio, l'uso di lambda nei metodi di estensione IEnumerable rispetto all'uso di lambda nei metodi di estensione IQueryable.

Ora, per rispondere alla domanda sul perché non sempre preferiamo l'albero delle espressioni, avete l'argomento della prestazione che MagnatLU ha già affermato.Se si accetta uno Expression e si chiama Compile per poterlo eseguire, sarà sempre più lento rispetto all'accettazione di un delegato.

C'è anche una differenza semantica tra i due, un delegato è solo un modo per eseguire del codice mentre un albero di espressioni è una descrizione del codice reale.

Se fossi in te, sceglierei di cambiare il nome del metodo che accetta l'espressione in qualcosa che riflette chiaramente ciò che aggiunge a quello delegato.

+1

Il fatto che un'espressione lambda possa essere interpretata in due modi non significa di per sé che non ci possa essere una regola per preferire l'una rispetto all'altra. Quindi non mi piace molto quella parte della tua risposta. Per il resto è grandioso. Grazie! –

0

Costruire e compilare un albero di espressioni richiede tempo e può esercitare una pressione significativa su GC. Non dovresti costruire e compilare espressioni in fase di esecuzione a meno che tu non sia realmente necessario.

Modifica: Inoltre, tenere presente che non tutte le espressioni o i metodi possono essere espressi come Expression<E>. Func<U, V> si preoccupa solo dei parametri 'e del tipo restituito e non ha tali limiti.

2

Come farà mai il compilatore a sapere se scegliere albero delle espressioni o delegato? È una tua decisione e non un compilatore.

Tutti i metodi linq forniscono sia i delegati sia le espressioni, ma sono estensioni sotto forma di tipo di destinazione.

Enumerable.Where<T>(this IEnumerable<T> en , Func<T,bool> filter) 
Queryable.Where<T>(this IQueryable<T> en , Expression<Func<T,bool>> filter) 

In base al tipo di destinazione, il compilatore può scegliere. Il compilatore è un programma, in effetti è una macchina ed è privo di contesto, non può prendere decisioni come umane su ciò che potrebbe essere migliore, segue solo le regole e quelle regole devono essere inequivocabili.

+1

La domanda non è il motivo per cui il compilatore lo etichetta come ambiguo: è perché è nella specifica per omissione. La domanda è perché i designer di lingue lo hanno lasciato ambiguo. –

+0

Perché non esiste una risposta specifica poiché ogni permutazione e combinazione non è possibile progettare, in questo modo ci saranno molte di queste regole che sono di bassa priorità e uno scenario di utilizzo raro. Non è pratico indovinare e aggiungere tutte le regole di progettazione. –

Problemi correlati