Dlaczego Func <T, bool> zamiast Predicate <T>?

210

To tylko pytanie z ciekawości zastanawiałem się, czy ktoś miał dobrą odpowiedź na:

W bibliotece klas .NET Framework mamy na przykład te dwie metody:

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
)

Dlaczego używają Func<TSource, bool>zamiast Predicate<TSource>? Wydaje się, że Predicate<TSource>jest używany tylko przez List<T>i Array<T>, gdy Func<TSource, bool>jest używany przez prawie wszystkich Queryable, a Enumerablemetody i sposoby przedłużania ... o co chodzi z tym?

Svish
źródło
21
Ugh tak, niekonsekwentne ich użycie doprowadza mnie również do szaleństwa.
George Mauer,

Odpowiedzi:

170

Chociaż Predicatezostał wprowadzony w tym samym czasie, List<T>a także Array<T>w .net 2.0, różne Funci Actionwarianty pochodzą z .net 3.5.

Więc te Funcpredykaty są używane głównie dla spójności w operatorach LINQ. Począwszy od .NET 3.5 na temat używania Func<T>i Action<T>ze stanów wytycznych :

Używaj nowych typów LINQ Func<>i Expression<>zamiast niestandardowych delegatów i predykatów

Jb Evain
źródło
7
Fajnie, nigdy wcześniej nie widziałem tych
gildii
6
Przyjmie to jako odpowiedź, ponieważ ma najwięcej głosów. a ponieważ Jon Skeet ma wiele powtórzeń ...: p
Svish
4
To dziwna wytyczna, jak napisano. Na pewno powinien zawierać „Używaj nowych typów LINQ” Func <> ”i„ Action <> ”[...]”. Wyrażenie <> to zupełnie inna rzecz.
Jon Skeet
3
Cóż, tak naprawdę musisz umieścić to w kontekście wytycznych, które dotyczą pisania dostawcy LINQ. Tak, tak, w przypadku operatorów LINQ wskazówką jest użycie Func <> i Expression <> jako parametrów metod rozszerzenia. Zgadzam się, że doceniłbym osobne wytyczne dotyczące korzystania z Func i Action
Jb Evain
Myślę, że Skeet ma na myśli to, że Wyrażenie <> nie jest wskazówką. Jest to funkcja kompilatora C #, która rozpoznaje ten typ i robi z nim coś innego.
Daniel Earwicker
116

Zastanawiałem się nad tym wcześniej. Lubię Predicate<T>delegata - jest miły i opisowy. Należy jednak wziąć pod uwagę przeciążenia Where:

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

To pozwala na filtrowanie również na podstawie indeksu pozycji. To miłe i konsekwentne, podczas gdy:

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

nie byłoby.

Jon Skeet
źródło
11
Predykat <int, bool> byłby nieco brzydki - predykat zwykle opiera się na jednej wartości. Oczywiście może to być Predicate <Pair <T, int >>, ale to nawet brzydsze :)
Jon Skeet
taka prawda ... hehe. Nie, myślę, że Func jest czystszy.
Svish,
2
Zaakceptowano inną odpowiedź z powodu wytycznych. Chciałbym zaznaczyć więcej niż jedną odpowiedź! Byłoby miło, gdybym mógł oznaczyć pewną liczbę odpowiedzi jako „Bardzo godne uwagi” lub „Mając też dobry punkt, który należy odnotować oprócz odpowiedzi”
Svish,
6
Hm, właśnie zobaczyłem, że źle napisałem ten pierwszy komentarz: P moją sugestią będzie oczywiście Predicate <T, int> ...
Svish 24.04.2009
4
@JonSkeet Wiem, że to pytanie jest stare i wszystkie, ale wiesz, co jest ironiczne? Nazwa parametru w Wheremetodzie rozszerzenia to predicate. Heh = P.
Conrad Clark,
32

Z pewnością faktycznym powodem użycia Funczamiast konkretnego delegata jest to, że C # traktuje osobno zadeklarowanych delegatów jako zupełnie różne typy.

Chociaż Func<int, bool>i Predicate<int>oba mają identyczne typy argumentów i powrotne, nie są one kompatybilne zadanie. Jeśli więc każda biblioteka zadeklaruje własny typ delegata dla każdego wzorca delegowania, biblioteki te nie będą mogły współpracować, dopóki użytkownik nie wstawi „mostkujących” delegatów w celu wykonania konwersji.

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

Zachęcając wszystkich do korzystania z Func, Microsoft ma nadzieję, że rozwiąże to problem niekompatybilnych typów delegatów. Wszyscy delegaci będą się dobrze bawić razem, ponieważ zostaną dopasowani na podstawie ich parametrów / typów powrotu.

Nie rozwiązuje wszystkich problemów, ponieważ Func(i Action) nie mogą mieć parametrów outani refparametrów, ale są one rzadziej używane.

Aktualizacja: w komentarzach Svish mówi:

Nadal zmiana typu parametru z Func na Predicate i odwrotnie nie wydaje się mieć żadnej różnicy? Przynajmniej kompiluje się bez żadnych problemów.

Tak, o ile twój program przypisuje metody tylko delegatom, tak jak w pierwszym wierszu mojej Mainfunkcji. Kompilator po cichu generuje kod do nowego obiektu delegowanego, który przekazuje dalej metodę. Tak więc w mojej Mainfunkcji mogłem zmienić x1się na typ ExceptionHandler2bez powodowania problemu.

Jednak w drugim wierszu próbuję przypisać pierwszego delegata innemu delegatowi. Nawet sądząc, że drugi typ delegata ma dokładnie ten sam typ parametru i zwracany typ, kompilator podaje błąd CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Może to wyjaśni:

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
}

Moja metoda IsNegativejest całkowicie dobra do przypisania do zmiennych pi f, o ile robię to bezpośrednio. Ale wtedy nie mogę przypisać jednej z tych zmiennych do drugiej.

Daniel Earwicker
źródło
Nadal zmiana typu parametru z Func <T, bool> na Predicate <T> i odwrotnie, nie wydaje się mieć żadnej różnicy? Przynajmniej kompiluje się bez żadnych problemów.
Svish,
Wygląda na to, że MS próbuje zniechęcić programistów do myślenia, że ​​są takie same. Zastanawiam się dlaczego?
Matt Kocaj,
1
Przełączanie rodzaju parametru robi różnicę, jeśli wyrażenie jesteś przejazdem do niego zostało zdefiniowane oddzielnie do wywołania metody, gdyż zostanie on wpisany jako albo Func<T, bool>czy Predicate<T>zamiast typ wywieść przez kompilator.
Adam Ralph
30

Porada (w wersji 3.5 i wyższej) polega na użyciu Action<...>i Func<...>- dla „dlaczego?” - jedną zaletą jest to, że „ Predicate<T>” ma sens tylko wtedy, gdy wiesz, co oznacza „orzecznik” - w przeciwnym razie musisz spojrzeć na przeglądarkę obiektów (itp.), aby znaleźć sygnatutę.

I odwrotnie, Func<T,bool>podąża za standardowym wzorem; Mogę od razu powiedzieć, że jest to funkcja, która przyjmuje Ti zwraca bool- nie trzeba rozumieć żadnej terminologii - po prostu zastosuj mój test prawdy.

W przypadku „predykatu” mogło to być w porządku, ale doceniam próbę standaryzacji. Pozwala to również na wiele parzystości z powiązanymi metodami w tym obszarze.

Marc Gravell
źródło