Mengapa Berfungsi <T, bool> daripada Predikat <T>?

211

Ini hanya pertanyaan rasa ingin tahu yang bertanya-tanya apakah ada yang punya jawaban bagus untuk:

Di perpustakaan .NET Framework Class kami memiliki misalnya dua metode ini:

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
)

Mengapa mereka menggunakan Func<TSource, bool>bukan Predicate<TSource>? Sepertinya Predicate<TSource>hanya digunakan oleh List<T>dan Array<T>, sementara Func<TSource, bool>digunakan oleh hampir semua Queryabledan Enumerablemetode dan metode penyuluhan ... ada apa dengan itu?

Svish
sumber
21
Ugh ya, penggunaan yang tidak konsisten ini membuatku gila juga.
George Mauer

Jawaban:

170

Sementara Predicatetelah diperkenalkan pada saat yang sama itu List<T>dan Array<T>, dalam. Net 2.0, perbedaan Funcdan Actionvarian berasal dari. Net 3.5.

Jadi Funcpredikat tersebut digunakan terutama untuk konsistensi dalam operator LINQ. Pada .net 3.5, tentang penggunaan Func<T>dan Action<T>status pedoman :

Gunakan tipe Func<>- tipe LINQ baru dan Expression<>alih-alih delegasi dan predikat kustom

Jb Evain
sumber
7
Keren, belum pernah melihat guildeline sebelumnya =)
: Svish
6
Akan menerima ini sebagai jawaban, karena memiliki suara terbanyak. dan karena Jon Skeet memiliki banyak perwakilan ...: p
Svish
4
Itu pedoman yang aneh seperti yang tertulis. Tentunya harus menyatakan "Apakah menggunakan tipe LINQ baru" Func <> "dan" Action <> "[...]". Ekspresi <> adalah hal yang sangat berbeda.
Jon Skeet
3
Sebenarnya, Anda harus menempatkan itu dalam konteks pedoman, yaitu tentang menulis penyedia LINQ. Jadi ya, untuk operator LINQ, pedomannya adalah menggunakan Func <> dan Expression <> sebagai parameter untuk metode ekstensi. Saya setuju saya akan menghargai pedoman terpisah tentang menggunakan Fungsi dan Aksi
Jb Evain
Saya pikir maksud Skeet adalah bahwa Ekspresi <> bukan pedoman. Ini adalah fitur kompiler C # untuk mengenali tipe itu dan melakukan sesuatu yang berbeda dengannya.
Daniel Earwicker
116

Saya sudah bertanya-tanya sebelumnya. Saya suka Predicate<T>delegasi - bagus dan deskriptif. Namun, Anda perlu mempertimbangkan kelebihan Where:

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

Itu memungkinkan Anda untuk memfilter berdasarkan pada indeks entri juga. Itu bagus dan konsisten, sedangkan:

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

tidak akan.

Jon Skeet
sumber
11
Predikat <int, bool> akan agak jelek - predikat biasanya (IME ilmu komputer) didasarkan pada nilai tunggal. Ini bisa menjadi Predikat <Pair <T, int >> tentu saja, tapi itu bahkan lebih buruk :)
Jon Skeet
sangat benar ... hehe. Tidak, saya kira Funcenya lebih bersih.
Svish
2
Menerima jawaban lain karena pedoman. Seandainya aku bisa menandai lebih dari satu jawaban! Akan lebih baik jika saya dapat menandai sejumlah jawaban sebagai "Sangat penting" atau "Juga memiliki poin yang baik yang harus dicatat di samping jawaban"
Svish
6
Hm, hanya melihat bahwa saya salah menulis komentar pertama: P saran saya tentu saja akan menjadi Predikat <T, int> ...
Svish
4
@JonSkeet Saya tahu pertanyaan ini sudah lama dan semua, tetapi tahu apa yang ironis? Nama parameter dalam Wheremetode ekstensi adalah predicate. Heh = P.
Conrad Clark
32

Tentunya alasan sebenarnya untuk menggunakan Funcalih-alih delegasi tertentu adalah bahwa C # memperlakukan delegasi yang dideklarasikan secara terpisah sebagai tipe yang sama sekali berbeda.

Meskipun Func<int, bool>dan Predicate<int>keduanya memiliki argumen dan tipe pengembalian yang sama, keduanya tidak kompatibel dengan penugasan. Jadi jika setiap perpustakaan mendeklarasikan tipe delegasinya sendiri untuk setiap pola delegasi, perpustakaan tersebut tidak akan dapat beroperasi kecuali pengguna memasukkan "bridging" delegate untuk melakukan konversi.

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

Dengan mendorong semua orang untuk menggunakan Fungsi, Microsoft berharap ini akan mengurangi masalah tipe delegasi yang tidak kompatibel. Delegasi setiap orang akan bermain bersama dengan baik, karena mereka hanya akan dicocokkan berdasarkan pada parameter / tipe pengembalian mereka.

Itu tidak menyelesaikan semua masalah, karena Func(dan Action) tidak dapat memiliki outatau refparameter, tetapi mereka kurang umum digunakan.

Pembaruan: dalam komentar Svish mengatakan:

Namun, beralih jenis parameter dari Func ke Predicate dan kembali, sepertinya tidak ada bedanya? Setidaknya masih dikompilasi tanpa masalah.

Ya, selama program Anda hanya menetapkan metode untuk didelegasikan, seperti pada baris pertama dari Mainfungsi saya . Kompiler secara diam-diam menghasilkan kode ke objek delegasi baru yang meneruskan ke metode. Jadi dalam Mainfungsi saya , saya bisa berubah x1menjadi tipe ExceptionHandler2tanpa menyebabkan masalah.

Namun, pada baris kedua saya mencoba untuk menetapkan delegasi pertama ke delegasi lain. Bahkan berpikir bahwa tipe delegasi ke-2 memiliki parameter yang persis sama dan tipe yang dikembalikan, kompiler memberikan kesalahan CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Mungkin ini akan membuatnya lebih jelas:

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
}

Metode saya IsNegativeadalah hal yang sangat baik untuk ditugaskan ke pdan fvariabel, selama saya melakukannya secara langsung. Tapi kemudian saya tidak bisa menetapkan salah satu variabel tersebut ke yang lain.

Daniel Earwicker
sumber
Namun, beralih jenis parameter dari Func <T, bool> ke Predicate <T> dan kembali, sepertinya tidak ada bedanya? Setidaknya masih dikompilasi tanpa masalah.
Svish
Tampaknya MS berusaha untuk mencegah pengembang dari berpikir ini sama. Kenapa ya?
Matt Kocaj
1
Memindah jenis parameter memang membuat perbedaan jika ekspresi yang Anda lewati telah ditentukan secara terpisah untuk pemanggilan metode, sejak itu akan diketik sebagai salah satu Func<T, bool>atau Predicate<T>lebih daripada memiliki jenis yang disimpulkan oleh kompiler.
Adam Ralph
30

Saran (dalam 3.5 dan di atas) adalah menggunakan Action<...>dan Func<...>- untuk "mengapa?" - satu keuntungan adalah " Predicate<T>" hanya bermakna jika Anda tahu apa arti "predikat" - jika tidak, Anda perlu melihat objek-browser (dll) untuk menemukan signatute.

Sebaliknya Func<T,bool>mengikuti pola standar; Saya dapat segera mengatakan bahwa ini adalah fungsi yang mengambil Tdan mengembalikan a bool- tidak perlu memahami terminologi apa pun - cukup terapkan tes kebenaran saya.

Untuk "predikat" ini mungkin baik-baik saja, tetapi saya menghargai upaya untuk membuat standar. Ini juga memungkinkan banyak kesamaan dengan metode terkait di daerah itu.

Marc Gravell
sumber