Pourquoi Func <T, bool> au lieu de Predicate <T>?

211

Ceci est juste une question de curiosité, je me demandais si quelqu'un avait une bonne réponse à:

Dans la bibliothèque de classes .NET Framework, nous avons par exemple ces deux méthodes:

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
)

Pourquoi utilisent-ils Func<TSource, bool>au lieu de Predicate<TSource>? On dirait que le Predicate<TSource>est uniquement utilisé par List<T>et Array<T>, alors qu'il Func<TSource, bool>est utilisé par à peu près tous Queryableet les Enumerableméthodes et les méthodes d'extension ... qu'est-ce qui se passe?

Svish
la source
21
Ugh oui, l'utilisation incohérente de ces derniers me rend fou aussi.
George Mauer

Réponses:

170

Bien qu'il Predicateait été introduit en même temps que List<T>et Array<T>dans .net 2.0, les différents Funcet Actionvariantes proviennent de .net 3.5.

Ces Funcprédicats sont donc principalement utilisés pour la cohérence des opérateurs LINQ. À partir de .net 3.5, à propos de l'utilisation Func<T>et Action<T>la directive indique :

Utilisez les nouveaux types LINQ Func<>et Expression<>au lieu des délégués et prédicats personnalisés

Jb Evain
la source
7
Cool, jamais vu ces lignes de guilde auparavant =)
Svish
6
Acceptera cela comme réponse, car il a la plupart des votes. et parce que Jon Skeet a beaucoup de représentants ...: p
Svish
4
C'est une directive bizarre telle qu'elle est écrite. Il devrait sûrement indiquer "Utilisez les nouveaux types LINQ" Func <> "et" Action <> "[...]". L'expression <> est une chose complètement différente.
Jon Skeet
3
Eh bien, en fait, vous devez placer cela dans le contexte de la directive, qui concerne l'écriture d'un fournisseur LINQ. Donc oui, pour les opérateurs LINQ, la directive est d'utiliser Func <> et Expression <> comme paramètres pour les méthodes d'extension. Je suis d'accord, j'apprécierais une directive distincte sur l'utilisation de Func et Action
Jb Evain
Je pense que le point de Skeet est que l'expression <> n'est pas une ligne directrice. C'est une fonctionnalité du compilateur C # pour reconnaître ce type et faire quelque chose de différent avec lui.
Daniel Earwicker
116

Je me suis déjà posé la question. J'aime le Predicate<T>délégué - c'est sympa et descriptif. Cependant, vous devez tenir compte des surcharges de Where:

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

Cela vous permet également de filtrer en fonction de l'index de l'entrée. C'est sympa et cohérent, alors que:

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

ne serait pas.

Jon Skeet
la source
11
Le prédicat <int, bool> serait quelque peu moche - un prédicat est généralement (IME de l'informatique) fondé sur une seule valeur. Cela pourrait être Predicate <Pair <T, int >> bien sûr, mais c'est encore plus laid :)
Jon Skeet
si vrai ... hehe. Non, je suppose que le Func est plus propre.
Svish
2
A accepté l'autre réponse en raison des directives. J'aimerais pouvoir marquer plus d'une réponse! Ce serait bien si je pouvais marquer un certain nombre de réponses comme "Très remarquable" ou "Avoir également un bon point qui devrait être noté en plus de la réponse"
Svish
6
Hm, je viens de voir que j'ai mal écrit ce premier commentaire: P ma suggestion allait bien sûr être Predicate <T, int> ...
Svish
4
@ JonSkeet Je sais que cette question est ancienne et tout, mais savez ce qui est ironique? Le nom du paramètre dans la Whereméthode d'extension est predicate. Heh = P.
Conrad Clark
32

La raison réelle de l'utilisation Funcau lieu d'un délégué spécifique est certainement que C # traite les délégués déclarés séparément comme des types totalement différents.

Même si Func<int, bool>et les Predicate<int>deux ont arguments identiques et types de retour, ils ne sont pas compatibles avec l' affectation. Ainsi, si chaque bibliothèque déclarait son propre type de délégué pour chaque modèle de délégué, ces bibliothèques ne seraient pas en mesure d'interopérer à moins que l'utilisateur n'insère des délégués de "pontage" pour effectuer des conversions.

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

En encourageant tout le monde à utiliser Func, Microsoft espère que cela résoudra le problème des types de délégués incompatibles. Les délégués de chacun joueront bien ensemble, car ils seront juste mis en correspondance en fonction de leurs paramètres / types de retour.

Il ne résout pas tous les problèmes, parce que Func(et Action) ne peuvent pas avoir outou refparamètres, mais ceux -ci sont moins fréquemment utilisés.

Mise à jour: dans les commentaires Svish dit:

Pourtant, le passage d'un type de paramètre de Func à Predicate et inversement ne semble pas faire de différence? Au moins, il compile toujours sans aucun problème.

Oui, tant que votre programme affecte uniquement des méthodes aux délégués, comme dans la première ligne de ma Mainfonction. Le compilateur génère silencieusement du code pour créer un nouvel objet délégué qui le transmet à la méthode. Donc, dans ma Mainfonction, je pouvais changer x1de type ExceptionHandler2sans causer de problème.

Cependant, sur la deuxième ligne, j'essaie d'affecter le premier délégué à un autre délégué. Même si le deuxième type de délégué a exactement les mêmes paramètres et types de retour, le compilateur donne une erreurCS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2' .

Peut-être que cela le rendra plus clair:

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
}

Ma méthode IsNegativeest une très bonne chose à attribuer aux variables pet f, tant que je le fais directement. Mais alors je ne peux pas assigner une de ces variables à l'autre.

Daniel Earwicker
la source
Pourtant, le passage d'un type de paramètre de Func <T, bool> à Predicate <T> et vice-versa, ne semble pas faire de différence? Au moins, il compile toujours sans aucun problème.
Svish
Il semble que MS essaie de décourager les développeurs de penser que ce sont les mêmes. Je me demande pourquoi?
Matt Kocaj
1
La commutation du type de paramètre fait une différence si l'expression que vous lui passez a été définie séparément pour l'appel de méthode, car il sera alors tapé comme Func<T, bool>ou Predicate<T>plutôt que d'avoir le type déduit par le compilateur.
Adam Ralph
30

Le conseil (en 3.5 et supérieur) est d'utiliser le Action<...>et Func<...>- pour le "pourquoi?" - un avantage est que "Predicate<T> " n'a de sens que si vous savez ce que signifie "prédicat" - sinon vous devez regarder le navigateur d'objets (etc) pour trouver le signataire.

Inverse Func<T,bool>suit un modèle standard; Je peux immédiatement dire que c'est une fonction qui prend un Tet retourne unbool - pas besoin de comprendre la terminologie - il suffit d'appliquer mon test de vérité.

Pour "prédicat", cela aurait pu être OK, mais j'apprécie la tentative de normalisation. Il permet également une grande parité avec les méthodes associées dans ce domaine.

Marc Gravell
la source