Tại sao Func <T, bool> thay vì Vị ngữ <T>?


210

Đây chỉ là một câu hỏi tò mò mà tôi đã tự hỏi nếu có ai có câu trả lời hay cho:

Trong Thư viện lớp .NET Framework, chúng tôi có ví dụ về hai phương thức sau:

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
)

Tại sao họ sử dụng Func<TSource, bool>thay vì Predicate<TSource>? Có vẻ như Predicate<TSource>chỉ được sử dụng bởi List<T>Array<T>, trong khi Func<TSource, bool>được sử dụng bởi khá nhiều tất cả QueryableEnumerablecác phương pháp và phương thức mở rộng ... chuyện gì vậy?


21
Vâng, việc sử dụng không nhất quán của những điều này cũng làm tôi phát điên.
George Mauer

Câu trả lời:


170

Mặc dù Predicateđã được giới thiệu cùng lúc List<T>Array<T>, trong .net 2.0, các biến thể FuncActionbiến thể khác nhau đến từ .net 3.5.

Vì vậy, các biến Funcvị ngữ được sử dụng chủ yếu cho tính nhất quán trong các toán tử LINQ. Tính đến .net 3.5, về việc sử dụng Func<T>Action<T>các quốc gia phương châm :

Sử dụng các loại LINQ mới Func<>Expression<>thay vì các đại biểu và vị từ tùy chỉnh


7
Thật tuyệt, chưa bao giờ thấy những bang hội đó trước đây =)
Svish

6
Sẽ chấp nhận điều này như câu trả lời, vì nó có hầu hết phiếu bầu. và bởi vì Jon Skeet có rất nhiều đại diện ...: p
Svish

4
Đó là một hướng dẫn kỳ quái như được viết. Chắc chắn nó phải ghi "Đừng sử dụng các loại LINQ mới" Func <> "và" Action <> "[...]". Biểu hiện <> là một điều hoàn toàn khác.
Jon Skeet

3
Chà, thực ra bạn phải đặt nó trong bối cảnh của hướng dẫn, đó là về việc viết một nhà cung cấp LINQ. Vì vậy, vâng, đối với các toán tử LINQ, hướng dẫn là sử dụng Func <> và Expression <> làm tham số cho các phương thức mở rộng. Tôi đồng ý Tôi đánh giá cao một hướng dẫn riêng về việc sử dụng Func và Action
Jb Evain 20/03/2016

Tôi nghĩ rằng quan điểm của Skeet là Biểu hiện <> không phải là một hướng dẫn. Đây là một tính năng trình biên dịch C # để nhận ra loại đó và làm một cái gì đó khác với nó.
Daniel Earwicker

116

Tôi đã tự hỏi điều này trước đây. Tôi thích Predicate<T>đại biểu - thật hay và mô tả. Tuy nhiên, bạn cần xem xét tình trạng quá tải của Where:

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

Điều đó cho phép bạn lọc dựa trên chỉ mục của mục. Đó là tốt đẹp và nhất quán, trong khi:

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

sẽ không


11
Vị ngữ <int, bool> sẽ hơi xấu - một vị ngữ thường là (IME của khoa học máy tính) dựa trên một giá trị duy nhất. Đó có thể là Vị ngữ <Cặp <T, int >>, nhưng điều đó thậm chí còn xấu hơn :)
Jon Skeet

rất đúng ... hehe. Không, tôi đoán Func sạch hơn.
Svish

2
Chấp nhận câu trả lời khác vì các hướng dẫn. Ước gì tôi có thể đánh dấu nhiều hơn một câu trả lời! Sẽ rất tuyệt nếu tôi có thể đánh dấu một số câu trả lời là "Rất đáng chú ý" hoặc "Cũng có một điểm tốt cần được lưu ý ngoài câu trả lời"
Svish

6
Hừm, chỉ thấy rằng tôi đã viết bình luận đầu tiên đó một cách sai lầm: P đề nghị của tôi tất nhiên sẽ là Dự đoán <T, int> ...
Svish

4
@JonSkeet Tôi biết câu hỏi này đã cũ và tất cả, nhưng biết điều gì mỉa mai? Tên của tham số trong Wherephương thức mở rộng là predicate. Heh = P.
Conrad Clark

32

Chắc chắn lý do thực tế cho việc sử dụng Functhay vì một đại biểu cụ thể là C # coi các đại biểu được tuyên bố riêng là các loại hoàn toàn khác nhau.

Mặc dù Func<int, bool>Predicate<int>cả hai đều có kiểu đối số và trả về giống hệt nhau, chúng không tương thích với gán. Vì vậy, nếu mỗi thư viện khai báo loại đại biểu riêng cho từng mẫu đại biểu, các thư viện đó sẽ không thể tương tác trừ khi người dùng chèn các đại biểu "bắc cầu" để thực hiện chuyển đổi.

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

Bằng cách khuyến khích mọi người sử dụng Func, Microsoft hy vọng rằng điều này sẽ làm giảm bớt vấn đề của các loại đại biểu không tương thích. Các đại biểu của mọi người sẽ chơi tốt với nhau, bởi vì họ sẽ được kết hợp dựa trên các loại tham số / trả về của họ.

Nó không giải quyết được tất cả các vấn đề, bởi vì Func(và Action) không thể có outhoặc reftham số, nhưng những thứ đó ít được sử dụng hơn.

Cập nhật: trong các bình luận Svish nói:

Tuy nhiên, việc chuyển đổi một loại tham số từ Func sang Vị ngữ và quay lại, dường như không có sự khác biệt nào? Ít nhất nó vẫn biên dịch mà không có bất kỳ vấn đề.

Có, miễn là chương trình của bạn chỉ gán các phương thức cho các đại biểu, như trong dòng đầu tiên của Mainhàm của tôi . Trình biên dịch âm thầm tạo mã cho một đối tượng ủy nhiệm mới chuyển tiếp đến phương thức. Vì vậy, trong Mainchức năng của mình , tôi có thể thay đổi x1thành loại ExceptionHandler2mà không gây ra vấn đề gì.

Tuy nhiên, trên dòng thứ hai tôi cố gắng chỉ định đại biểu đầu tiên cho một đại biểu khác. Thậm chí còn nghĩ rằng loại đại biểu thứ 2 có chính xác cùng loại tham số và kiểu trả về, trình biên dịch đưa ra lỗi CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Có lẽ điều này sẽ làm cho nó rõ ràng hơn:

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
}

Phương pháp của tôi IsNegativelà một điều hoàn toàn tốt để gán cho pfcác biến, miễn là tôi làm như vậy trực tiếp. Nhưng sau đó tôi không thể gán một trong các biến đó cho biến kia.


Tuy nhiên, việc chuyển đổi một loại tham số từ Func <T, bool> sang Vị ngữ <T> và quay lại, dường như không có sự khác biệt nào? Ít nhất nó vẫn biên dịch mà không có bất kỳ vấn đề.
Svish

Có vẻ như MS đang cố gắng ngăn cản các nhà phát triển nghĩ rằng những điều này giống nhau. Tôi tự hỏi tại sao?
Matt Kocaj

1
Việc chuyển loại tham số sẽ tạo ra sự khác biệt nếu biểu thức bạn chuyển đến nó đã được xác định riêng cho lệnh gọi phương thức, từ đó nó sẽ được gõ dưới dạng Func<T, bool>hoặc Predicate<T>thay vì có kiểu được trình biên dịch suy ra.
Adam Ralph

30

Lời khuyên (trong 3.5 trở lên) là sử dụng Action<...>Func<...>- cho "tại sao?" - một lợi thế là " Predicate<T>" chỉ có ý nghĩa nếu bạn biết "vị ngữ" nghĩa là gì - nếu không, bạn cần nhìn vào trình duyệt đối tượng (v.v.) để tìm ra chữ ký.

Ngược lại Func<T,bool>theo một mô hình chuẩn; Tôi có thể ngay lập tức nói rằng đây là một hàm lấy Tvà trả về một bool- không cần hiểu bất kỳ thuật ngữ nào - chỉ cần áp dụng thử nghiệm sự thật của tôi.

Đối với "vị ngữ" điều này có thể đã ổn, nhưng tôi đánh giá cao nỗ lực chuẩn hóa. Nó cũng cho phép rất nhiều sự tương đương với các phương pháp liên quan trong khu vực đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.