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?


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


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.


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.


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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.