Perché Func <T, bool> invece di Predicate <T>?


211

Questa è solo una domanda curiosa che mi chiedevo se qualcuno avesse una buona risposta a:

Nella libreria di classi .NET Framework abbiamo ad 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 Predicate<TSource>sia usato solo da List<T>e Array<T>, mentre Func<TSource, bool>è usato praticamente da tutti Queryablee Enumerablemetodi e metodi di estensione ... che succede?


21
Sì, l'uso incoerente di questi mi fa impazzire.
George Mauer,

Risposte:


170

Mentre Predicateè stato introdotto contemporaneamente List<T>e Array<T>, in .net 2.0, le diverse Funce Actionvarianti derivano da .net 3.5.

Quindi questi Funcpredicati sono usati principalmente per coerenza negli operatori LINQ. A partire da .net 3.5, sull'uso Func<T>e Action<T>le linee guida :

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


7
Fantastico, non avevo mai visto quelle linee della gilda prima =)
Svish,

6
Accetterò questo come risposta, poiché ha il maggior numero di voti. e poiché Jon Skeet ha molti rappresentanti ...: p
Svish,

4
Questa è una linea guida bizzarra come scritta. Sicuramente dovrebbe indicare "Usa i nuovi tipi LINQ" Func <> "e" Azione <> "[...]". L'espressione <> è una cosa completamente diversa.
Jon Skeet,

3
Bene, in realtà devi inserirlo nel contesto delle linee guida, che riguardano la scrittura di un provider LINQ. Quindi sì, per gli operatori LINQ, la linea guida è usare Func <> ed Expression <> come parametri per i metodi di estensione. Sono d'accordo che apprezzerei una linea guida separata sull'uso di Func e Action
Jb Evain,

Penso che il punto di Skeet sia che Expression non sia una linea guida. È una funzione del compilatore C # per riconoscere quel tipo e fare qualcosa di diverso con esso.
Daniel Earwicker,

116

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

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

Ciò consente di filtrare anche in base all'indice della voce. È carino e coerente, mentre:

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

non sarebbe.


11
Il predicato <int, bool> sarebbe alquanto brutto - un predicato è di solito (IME di informatica) basato su un singolo valore. Potrebbe essere Predicato <Pair <T, int >> ovviamente, ma è ancora più brutto :)
Jon Skeet

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

2
Ha accettato l'altra risposta a causa delle linee guida. Vorrei poter contrassegnare più di una risposta però! Sarebbe bello se potessi contrassegnare un numero di risposte come "Molto degno di nota" o "Avere anche un buon punto che dovrebbe essere notato in aggiunta alla risposta"
Svish

6
Hm, ho appena visto che ho scritto il primo commento in modo errato: P il mio suggerimento sarebbe stato ovviamente Predicato <T, int> ...
Svish

4
@JonSkeet So che questa domanda è vecchia e tutto, ma so cos'è ironico? Il nome del parametro nel Wheremetodo di estensione è predicate. Heh = P.
Conrad Clark,

32

Sicuramente il vero motivo dell'utilizzo al Funcposto di un delegato specifico è che C # considera i delegati dichiarati separatamente come tipi totalmente diversi.

Anche se Func<int, bool>e Predicate<int>entrambi hanno identici tipi di argomento e di ritorno, non sono assegnazione compatibile. Pertanto, se ogni libreria dichiarasse il proprio tipo di delegato per ciascun modello di delegato, tali librerie non sarebbero in grado di interagire a meno che l'utente non inserisca delegati "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 a utilizzare Func, Microsoft spera che questo allevierà il problema dei tipi di delegati incompatibili. Tutti i delegati giocheranno bene insieme, perché saranno semplicemente abbinati in base al loro parametro / tipi di ritorno.

Non risolve tutti i problemi, perché Func(e Action) non possono avere parametri outo ref, ma quelli sono meno comunemente usati.

Aggiornamento: nei commenti Svish dice:

Tuttavia, cambiando un tipo di parametro da Func a Predicate e viceversa, non sembra fare alcuna differenza? Almeno si compila ancora senza problemi.

Sì, purché il programma assegni solo metodi ai delegati, come nella prima riga della mia Mainfunzione. Il compilatore genera silenziosamente il codice per un nuovo oggetto delegato che inoltra al metodo. Quindi, nella mia Mainfunzione, potrei cambiare x1per essere di tipo ExceptionHandler2senza causare un problema.

Tuttavia, sulla 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 tipi di restituzione, il compilatore genera un errore CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Forse questo renderà 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 cosa perfettamente valida da assegnare alle variabili pe f, purché lo faccia direttamente. Ma poi non posso assegnare una di quelle variabili all'altra.


Tuttavia, cambiando un tipo di parametro da Func <T, bool> a Predicate <T> e viceversa, non sembra fare alcuna differenza? Almeno si compila ancora senza problemi.
Svish,

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

1
Il cambio del tipo di parametro fa la differenza se l'espressione che si sta passando è stata definita separatamente per la chiamata al metodo, da allora verrà digitata come una Func<T, bool>o Predicate<T>piuttosto che con il tipo inferito dal compilatore.
Adam Ralph,

30

Il consiglio (in 3.5 e sopra) è di usare Action<...>e Func<...>- per il "perché?" - un vantaggio è che " Predicate<T>" è significativo solo se sai cosa significa "predicato" - altrimenti devi cercare il browser degli oggetti (ecc.) per trovare il signatute.

Al contrario Func<T,bool>segue uno schema standard; Posso immediatamente dire che questa è una funzione che accetta a Te restituisce a bool- non è necessario comprendere alcuna terminologia - applica semplicemente il mio test di verità.

Per "predicato" questo potrebbe essere stato OK, ma apprezzo il tentativo di standardizzare. Inoltre consente molta parità con i relativi metodi in quella zona.

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.