Sử dụng các đại diện trong C #


79

Trong ngôn ngữ C # và .NET framework, bạn có thể giúp tôi hiểu về các đại diện không? Tôi đang cố gắng kiểm tra một số mã, và thấy rằng kết quả tôi nhận được thật bất ngờ đối với tôi. Nó đây:

class Program
{
    public static int I = 0;

    static Func<string> del = new Func<string>(I.ToString);

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del());
    }
}

Câu trả lời là 0, nhưng không phải 10. Tại sao?


12
@Rotem: Không, anh ấy không.
Daniel Hilgarth

3
@Rotem - Đây là một khai báo ủy quyền. Thêm ()sẽ gọi ToString.
Oded

1
Xin lỗi, không bao giờ sử dụng Funcs, là một đoán :)
Rotem

2
+1 cho một câu hỏi hay, được hỏi nhiều. Một ví dụ tuyệt vời về cách một câu hỏi tưởng như đơn giản có thể làm nổi bật một lĩnh vực ngôn ngữ / nền tảng kém hiểu biết.
Martin

5
Một cá thể đại biểu (unicast) có thể trỏ đến một phương thức thể hiện hoặc một staticphương thức. Khi nó đại diện cho một phương thức thể hiện, người ủy quyền giữ cả đối tượng "đích" để gọi phương thức và thông tin phương thức. Vì vậy, khi bạn nói del = I.ToString;, delsẽ giữ đối tượng Iở đây là Int32(kiểu giá trị không thay đổi). Khi bạn sử dụng một hàm ẩn danh, del = () => I.ToString();trình biên dịch sẽ tạo một phương thức static string xxx() { return I.ToString(); }delđối tượng giữ phương thức được tạo đó.
Jeppe Stig Nielsen,

Câu trả lời:


79

Lý do là như sau:

Cách bạn khai báo ủy quyền nó trỏ trực tiếp đến ToStringphương thức của thể hiện static int. Nó được chụp tại thời điểm tạo ra.

Như flipndeberg đã chỉ ra trong các bình luận bên dưới, mỗi đại biểu có một mục tiêu và một phương pháp để thực hiện trên mục tiêu.

Trong trường hợp này, phương thức được thực thi rõ ràng là ToStringphương thức. Phần thú vị là thể hiện mà phương thức được thực thi: Đó là thể hiện của Itại thời điểm tạo, có nghĩa là người ủy quyền không sử dụng Iđể lấy thể hiện đó sử dụng nhưng nó lưu trữ tham chiếu đến chính thể hiện đó.

Sau đó, bạn thay đổi Ithành một giá trị khác, về cơ bản sẽ gán cho nó một phiên bản mới. Điều này không làm thay đổi một cách kỳ diệu phiên bản được chụp trong ủy quyền của bạn, tại sao phải làm vậy?

Để có được kết quả như mong đợi, bạn cần phải thay đổi ủy quyền thành:

static Func<string> del = new Func<string>(() => I.ToString());

Như vậy, ủy nhiệm trỏ đến một phương thức ẩn danh thực thi ToStringtrên phương thức hiện Itại tại thời điểm thực thi ủy quyền.

Trong trường hợp này, phương thức được thực thi là một phương thức ẩn danh được tạo trong lớp mà trong đó ủy nhiệm được khai báo. Ví dụ là null vì nó là một phương thức tĩnh.

Hãy xem mã mà trình biên dịch tạo ra cho phiên bản thứ hai của đại biểu:

private static Func<string> del = new Func<string>(UserQuery.<.cctor>b__0);
private static string cctor>b__0()
{
    return UserQuery.I.ToString();
}

Như bạn có thể thấy, nó là một phương pháp bình thường để thực hiện một cái gì đó . Trong trường hợp của chúng ta, nó trả về kết quả của việc gọi ToStringđối tượng hiện tại của I.


1
@flindeberg: Bạn thậm chí có thể sử dụng lớp của riêng mình thay vì int. Nó sẽ vẫn hoạt động như cũ, bởi vì lý do cơ bản không thay đổi: Đại biểu trỏ đến hiện thân cụ thể của ToString trên một đối tượng cụ thể. Không quan trọng đây là kiểu tham chiếu hay kiểu giá trị.
Daniel Hilgarth

3
@ user1859587: Một ủy quyền có một phương thức và một đích (phiên bản), điều quan trọng là nó nắm bắt phiên bản của bạn hoặc phiên bản của hàm lambda với lần lượt chứa các tham chiếu đến phiên bản.
flndeberg

1
@ user1859587: Không có chi. BTW: Tôi đã cố gắng cập nhật câu trả lời để làm rõ hơn một chút về những gì đang diễn ra ở đây. Bạn có thể muốn đọc lại nó :-)
Daniel Hilgarth

3
Daniel, chỉ để xác nhận nhận xét của flipndeberg: câu trả lời của bạn là đúng nhưng nhận xét của bạn về quyền anh thì không. user1859587 là đúng: hành vi được quan sát là hệ quả của thực tế là người ủy quyền bắt người nhận cuộc gọi. Mặc dù người nhận lệnh gọi đến ToString trên int sẽ là một tham chiếu đến một biến int, người ủy quyền không có cách nào để đặt một tham chiếu đến một biến int trên heap; tham chiếu đến các biến chỉ có thể được lưu trữ tạm thời. Vì vậy, nó thực hiện điều tốt nhất tiếp theo: nó đóng hộp int và tạo tham chiếu đến vị trí heap đó.
Eric Lippert

10
Một hệ quả thú vị của thực tế rằng bộ nhận được đóng hộp là không thể tạo đại biểu cho GetValueOrDefault () trên một int nullable, bởi vì việc đóng hộp một int nullable tạo ra một int đóng hộp, không phải int nullable được đóng hộp và một int đóng hộp có không có phương thức GetValueOrDefault ().
Eric Lippert

4

Bạn cần chuyển vào Ihàm của mình để hàm này I.ToString()có thể được thực thi vào thời điểm thích hợp (thay vì lúc hàm được tạo).

class Program
{
    public static int I = 0;

    static Func<int, string> del = num => num.ToString();

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del(I));
    }
}

1

Đây là cách điều này nên được thực hiện:

using System;

namespace ConsoleApplication1
{

    class Program
    {
        public static int I = 0;

        static Func<string> del = new Func<string>(() => {
            return I.ToString();
        });

        static void Main(string[] args)
        {
            I = 10;
            Console.WriteLine("{0}", del());
        }
    }
}

0

Cho phép ủy quyền C # đóng gói cả một đối tượng và cá thể và một phương thức. Khai báo ủy nhiệm xác định một lớp có nguồn gốc từ lớp System.Delegate. Một cá thể ủy nhiệm đóng gói một danh sách lời gọi, là một danh sách một hoặc nhiều phương thức, mỗi phương thức được gọi là thực thể có thể gọi.

học thêm hình thức

http://asp-net-by-parijat.blogspot.in/2015/08/what-is-delegates-in-c-how-to-declare.html


-2

Suy đoán của tôi là bởi vì int được truyền bởi các giá trị không phải tham chiếu và vì lý do đó khi tạo đại biểu, nó là đại biểu cho phương thức ToString của giá trị hiện tại của "I" (0).


2
Phỏng đoán của bạn không đúng. Điều này không liên quan gì đến kiểu giá trị so với kiểu tham chiếu. Điều tương tự cũng sẽ xảy ra với các loại tham chiếu.
Daniel Hilgarth

Trên thực tế, nếu ví dụ, chúng ta sử dụng một cá thể lớp và ToString đang thao tác dữ liệu cá thể để lấy giá trị trả về, nó sẽ tạo ra giá trị trả về của trạng thái lớp hiện tại, không phải trạng thái của lớp khi đại biểu được tạo. Hàm không chạy khi đại biểu được tạo và chỉ có một thể hiện của lớp.
Yshayy

Hàm <string> (() => I.ToString ()) Cũng nên hoạt động vì chúng ta không sử dụng "I" cho đến khi gọi phương thức.
Yshayy

Nhưng điều đó không tương đương với những gì đang diễn ra ở đây. Nếu bạn sử dụng lớp Foothay vì intvà thay đổi dòng I = 10thành I = new Foo(10)bạn sẽ có kết quả chính xác giống như với mã hiện tại. I.Value = 10là một cái gì đó khác hoàn toàn. Điều này không chỉ định một phiên bản mới cho I. Nhưng việc gán một phiên bản mới Ilà điểm quan trọng ở đây.
Daniel Hilgarth

2
ok, vấn đề là gán lại "I", nếu "I" có thể thay đổi được và chúng ta thay đổi đối tượng mà không gán lại I, nó sẽ hoạt động. trong ví dụ này, chúng tôi không thể làm điều đó vì tôi là int (không thể thay đổi).
Yshayy
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.