Tại sao biên dịch OK, khi tôi sử dụng phương thức Invoke và không OK khi tôi trả lại trực tiếp Func <int, int>?


28

Tôi không hiểu trường hợp này:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

Tại sao biên dịch OK khi tôi sử dụng Invokephương thức và không OK khi tôi quay lại csharp Func<int,int>trực tiếp?


Bạn có một đại biểu có nghĩa là bạn đang nhận được một số loại sự kiện. Invoke ngăn chặn ngoại lệ luồng chéo và cho phép nhiều quá trình truy cập vào đối tượng.
jdweng

Lưu ý rằng bạn sẽ thấy vấn đề này ngay cả khi bạn sử dụng hai đại biểu có vẻ giống nhau như delegate void test1(int i);delegate void test2(int i);
Matthew Watson

Câu trả lời:


27

Có hai điều bạn cần biết để hiểu hành vi này.

  1. Tất cả các đại biểu xuất phát từ System.Delegate, nhưng các đại biểu khác nhau có các loại khác nhau và do đó không thể được chỉ định cho nhau.
  2. Ngôn ngữ C # cung cấp xử lý đặc biệt để gán phương thức hoặc lambda cho đại biểu .

Bởi vì các đại biểu khác nhau có các loại khác nhau, điều đó có nghĩa là bạn không thể chỉ định một đại biểu loại này cho loại khác.

Ví dụ: đã cho:

delegate void test1(int i);
delegate void test2(int i);

Sau đó:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

Dòng đầu tiên ở trên biên dịch OK vì nó đang sử dụng cách xử lý đặc biệt để gán lambda hoặc phương thức cho một đại biểu.

Trong thực tế, dòng này được viết lại một cách hiệu quả như thế này bởi trình biên dịch:

test1 a = new test1(Console.WriteLine);

Dòng thứ hai ở trên không biên dịch vì nó đang cố gán một thể hiện của một loại cho một loại không tương thích khác.

Theo như các loại, không có sự phân công tương thích giữa test1test2bởi vì chúng là các loại khác nhau.

Nếu nó giúp suy nghĩ về nó, hãy xem xét hệ thống phân cấp lớp này:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

Đoạn mã sau sẽ KHÔNG biên dịch, mặc dù Test1Test2xuất phát từ cùng một lớp cơ sở:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

Điều này giải thích tại sao bạn không thể chỉ định một loại đại biểu cho loại khác. Đó chỉ là ngôn ngữ C # bình thường.

Tuy nhiên, điều quan trọng là phải hiểu lý do tại sao bạn được phép gán một phương thức hoặc lambda cho một đại biểu tương thích. Như đã lưu ý ở trên, đây là một phần hỗ trợ ngôn ngữ C # cho các đại biểu.

Vì vậy, cuối cùng để trả lời câu hỏi của bạn:

Khi bạn sử dụng, Invoke()bạn đang chỉ định một cuộc gọi METHOD cho đại biểu bằng cách xử lý ngôn ngữ C # đặc biệt để gán các phương thức hoặc lambdas cho một đại biểu thay vì cố gắng gán một loại không tương thích - do đó nó sẽ biên dịch OK.

Để hoàn toàn rõ ràng, mã sẽ biên dịch trong OP của bạn:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

Được thực sự chuyển đổi khái niệm thành một cái gì đó như:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

Trong khi đó mã lỗi đang cố gán giữa hai loại không tương thích:

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}

6

Trong trường hợp thứ hai, flà loại Func<int, int>, nhưng phương thức được cho là trả về a test. Đây là các loại không liên quan (ủy nhiệm), không thể chuyển đổi lẫn nhau, do đó xảy ra lỗi trình biên dịch. Bạn có thể đi đến phần này của thông số ngôn ngữ và tìm kiếm "đại biểu". Bạn sẽ không tìm thấy đề cập đến chuyển đổi giữa các đại biểu có cùng chữ ký.

Tuy nhiên, trong trường hợp đầu tiên, f.Invokelà một biểu thức nhóm phương thức , không thực sự có một kiểu. Trình biên dịch C # sẽ chuyển đổi các biểu thức nhóm phương thức thành các loại đại biểu cụ thể theo ngữ cảnh, thông qua một chuyển đổi nhóm phương thức .

(Trích dẫn viên đạn thứ 5 ở đây , nhấn mạnh vào tôi)

Một biểu thức được phân loại là một trong những điều sau đây:

  • ...

  • Một nhóm phương thức, là một tập hợp các phương thức bị quá tải do việc tra cứu thành viên. [...] Một nhóm phương thức được cho phép trong invocation_expression, ủy nhiệm_creation_expression và là phía bên trái của một istoán tử và có thể được chuyển đổi hoàn toàn thành một loại ủy nhiệm tương thích.

Trong trường hợp này, nó được chuyển đổi thành testloại đại biểu.

Nói cách khác, return fkhông hoạt động vì fđã có một loại, nhưng f.Invokechưa có loại nào.


2

Vấn đề ở đây là loại tương thích:

Sau đây là định nghĩa của đại biểu Func từ các nguồn MSDN:

public delegate TResult Func<in T, out TResult>(T arg);

Nếu bạn thấy không có mối quan hệ trực tiếp giữa Func được đề cập ở trên và Đại biểu được xác định của bạn:

public delegate int test(int i);

Tại sao đoạn 1 biên dịch:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

Đại biểu được so sánh bằng chữ ký, là tham số đầu vào và kết quả đầu ra, cuối cùng, Đại biểu là con trỏ Hàm và hai hàm chỉ có thể được so sánh thông qua chữ ký. Trong thời gian chạy, phương thức được gọi thông qua Func được gán cho Testđại biểu, vì Chữ ký giống nhau nên nó hoạt động trơn tru. Đó là một phép gán con trỏ hàm, trong đó Testđại biểu bây giờ sẽ gọi phương thức được chỉ định bởi đại biểu Func

Tại sao Đoạn 2 không biên dịch được

Giữa Func và đại biểu thử nghiệm, không có khả năng tương thích loại / gán, Func không thể điền vào như một phần của quy tắc hệ thống Loại. Ngay cả khi kết quả của nó có thể được chỉ định và điền vào test delegatenhư được thực hiện trong trường hợp đầu tiên.

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.