web-dev-qa-db-fra.com

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

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> n'est utilisé que par List<T> et Array<T>, tandis que Func<TSource, bool> est utilisé par à peu près toutes les méthodes Queryable et Enumerable et les méthodes d'extension ... qu'est-ce qui se passe avec ça?

206
Svish

Alors que Predicate a été introduit en même temps que List<T> et Array<T>, dans .net 2.0, les différentes variantes Func et Action proviennent de .net 3.5.

Donc, ces prédicats Func sont principalement utilisés pour la cohérence des opérateurs LINQ. A partir de .net 3.5, sur l'utilisation de Func<T> et Action<T> le états indicatifs :

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

161
Jb Evain

Je me suis déjà demandé ça. J'aime le Predicate<T> délégué - c'est gentil et descriptif. Cependant, vous devez prendre en compte les 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 gentil et cohérent, alors que:

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

ne serait pas.

111
Jon Skeet

La raison réelle d'utiliser Func au lieu d'un délégué spécifique est 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 Predicate<int> les deux ont des types d'argument et de retour identiques, ils ne sont pas compatibles avec les affectations. 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 pourraient pas interagir à moins que l'utilisateur insère des délégués "pontant" 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. Tous les délégués joueront bien ensemble, car ils seront simplement mis en correspondance en fonction de leurs types de paramètre/retour.

Cela ne résout pas tous les problèmes, car Func (et Action) ne peut pas avoir de paramètres out ou ref, mais ceux-ci sont moins utilisés.

Mise à jour: dans les commentaires, Svish dit:

Néanmoins, changer 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 n'affecte que des méthodes aux délégués, comme dans la première ligne de ma fonction Main. Le compilateur génère silencieusement du code pour créer un nouvel objet délégué, puis le transférer à la méthode. Donc, dans ma fonction Main, je pourrais changer x1 être de type ExceptionHandler2 sans causer de problème.

Cependant, sur la deuxième ligne, j'essaie d'assigner 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 l'erreur CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Cela clarifiera peut-être:

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 IsNegative est une très bonne chose à affecter aux variables p et f, tant que je le fais directement. Mais alors je ne peux pas assigner l'une de ces variables à l'autre.

30
Daniel Earwicker

Le conseil (en 3.5 et plus) 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 consulter le navigateur d’objets (etc.) pour trouver la signature.

Inversement Func<T,bool> suit un modèle standard; Je peux immédiatement dire qu'il s'agit d'une fonction qui prend un T et retourne un bool - vous n'avez pas besoin de comprendre la terminologie - appliquez simplement mon test de vérité.

Pour "prédicat" cela aurait pu être correct, mais j'apprécie la tentative de standardisation. Cela permet aussi beaucoup de parité avec les méthodes associées dans ce domaine.

27
Marc Gravell