2009-03-20 16 views
195

Questa è solo una domanda curiosità mi chiedevo se qualcuno ha avuto una buona risposta a:Perché Func <T,bool> invece di Predicato <T>?

Nella libreria di classi .NET Framework abbiamo per esempio questi due metodi:

public static IQueryable<TSource> Where<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, bool>> predicate 
) 

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate 
) 

Perché usano Func<TSource, bool> invece di Predicate<TSource>? Sembra che lo Predicate<TSource> sia usato solo da List<T> e Array<T>, mentre lo Func<TSource, bool> è utilizzato da quasi tutti i metodi Queryable e Enumerable e metodi di estensione ... che succede?

+15

Ugh sì, l'uso incoerente di questi mi fa impazzire anche io. –

risposta

155

Mentre Predicate è stato introdotto allo stesso tempo che List<T> e Array<T>, in .net 2.0, le diverse Func e Action varianti provengono da NET 3.5.

Quindi i predicati Func vengono utilizzati principalmente per coerenza negli operatori LINQ. Come di .net 3.5, sull'utilizzo Func<T> e Action<T> il guideline states:

fare utilizzare i nuovi tipi di LINQ Func<> e Expression<> invece di personalizzati delegati e predicati

+7

Fresco, mai visto quelle gilde precedenti =) – Svish

+4

Accetterà questo come risposta, dal momento che ha più voti. e perché Jon Skeet ha un sacco di rep ...: p – Svish

+4

Questa è una guida bizzarra come scritto. Sicuramente dovrebbe indicare "Usa i nuovi tipi LINQ" Func <> "e" Action <> "[...]". L'espressione <> è una cosa completamente diversa. –

106

mi sono chiesto questo prima. Mi piace il delegato Predicate<T>: è bello e descrittivo. Tuttavia, è necessario considerare i sovraccarichi di Where:

Where<T>(IEnumerable<T>, Func<T, bool>) 
Where<T>(IEnumerable<T>, Func<T, int, bool>) 

che permette di filtrare in base all'indice della voce pure. Questo è bello e coerente, mentre:

Where<T>(IEnumerable<T>, Predicate<T>) 
Where<T>(IEnumerable<T>, Func<T, int, bool>) 

non sarebbe.

+4

Potrebbe avere un Predicato ? Ma vedo il tuo punto =) – Svish

+11

Il predicato sarebbe un po 'brutto - un predicato di solito è (IME di informatica) basato su un singolo valore. Potrebbe essere Predicato > naturalmente, ma questo è ancora più brutto :) –

+0

così vero ... hehe. No, immagino che Func sia più pulito. – Svish

26

Il consiglio (in 3.5 e sopra) è quello di utilizzare il Action<...> e - per il "perché?" - un vantaggio è che "Predicate<T>" è significativo solo se si conosce il significato di "predicato", altrimenti è necessario guardare il browser dell'oggetto (ecc.) per trovare il codice.

Al contrario, Func<T,bool> segue un modello standard; Posso subito dire che questa è una funzione che prende uno T e restituisce un bool - non è necessario comprendere alcuna terminologia - basta applicare il mio test di verità.

Per "predicato" questo potrebbe essere stato OK, ma apprezzo il tentativo di standardizzare. Permette anche molta parità con i metodi correlati in quell'area.

+0

Buon punto pure. Ci piace la standardizzazione =) – Svish

28

Sicuramente il motivo reale dell'utilizzo di Func invece di un delegato specifico è che C# tratta i delegati dichiarati separatamente come tipi completamente diversi.

Anche se Func<int, bool> e Predicate<int> hanno entrambi argomenti e tipi di ritorno identici, non sono compatibili con l'assegnazione. Pertanto, se ogni libreria dichiarava il proprio tipo di delegato per ciascun modello delegato, tali librerie non sarebbero in grado di interoperare a meno che l'utente inserisca delegati "a ponte" per eseguire conversioni.

// declare two delegate types, completely identical but different names: 
    public delegate void ExceptionHandler1(Exception x); 
    public delegate void ExceptionHandler2(Exception x); 

    // a method that is compatible with either of them: 
    public static void MyExceptionHandler(Exception x) 
    { 
     Console.WriteLine(x.Message); 
    } 

    static void Main(string[] args) 
    { 
     // can assign any method having the right pattern 
     ExceptionHandler1 x1 = MyExceptionHandler; 

     // and yet cannot assign a delegate with identical declaration! 
     ExceptionHandler2 x2 = x1; // error at compile time 
    } 

Incoraggiando tutti di utilizzare Func, Microsoft spera che questo possa alleviare il problema dei tipi di delegati incompatibili. I delegati di tutti giocheranno bene insieme, perché saranno abbinati solo in base ai loro parametri/tipi di ritorno.

non risolve tutti i problemi, perché Func (e Action) non possono avere out o ref parametri, ma questi sono meno comunemente usato.

Aggiornamento: nei commenti Svish dice:

Ancora, il passaggio di un tipo di parametro da Func per predicato e indietro, non sembra fare alcuna differenza ? Almeno compila ancora senza problemi.

Sì, purché il programma assegni solo metodi ai delegati, come nella prima riga della mia funzione Main. Il compilatore genera silenziosamente il codice su un nuovo oggetto delegato che inoltra al metodo. Quindi, nella mia funzione Main, potrei cambiare x1 per essere di tipo ExceptionHandler2 senza causare problemi.

Tuttavia, nella seconda riga provo ad assegnare il primo delegato a un altro delegato. Anche se il secondo tipo di delegato ha esattamente lo stesso parametro e i tipi di ritorno, il compilatore restituisce l'errore CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Forse questo modo sarà più chiaro:

public static bool IsNegative(int x) 
{ 
    return x < 0; 
} 

static void Main(string[] args) 
{ 
    Predicate<int> p = IsNegative; 
    Func<int, bool> f = IsNegative; 

    p = f; // Not allowed 
} 

Il mio metodo IsNegative è una perfetta buona cosa per assegnare alle variabili p e f, fino a quando lo faccio direttamente. Ma poi non posso assegnare una di queste variabili all'altra.

+0

Ancora, cambiare un tipo di parametro da Func a Predicato e viceversa, non sembra fare alcuna differenza? Almeno si compila ancora senza problemi. – Svish

+0

Sembra che MS stia cercando di scoraggiare gli sviluppatori dal pensare che siano gli stessi. Mi chiedo perché? –

+0

Cambiare il tipo di parametro fa la differenza se l'espressione che si sta passando ad essa è stata definita separatamente dalla chiamata al metodo, da allora verrà digitata come "Func " o "Predicato " invece di avere il tipo inferito da il compilatore. –

Problemi correlati