2009-09-14 12 views
12

Xem xét các mã ví dụ sau:Tạo hai trường hợp đại biểu cho phương thức ẩn danh cùng không bằng nhau

static void Main(string[] args) 
{ 
    bool same = CreateDelegate(1) == CreateDelegate(1); 
} 

private static Action CreateDelegate(int x) 
{ 
    return delegate { int z = x; }; 
} 

Bạn sẽ tưởng tượng rằng hai trường hợp đại biểu sẽ so sánh để được bình đẳng, cũng giống như họ sẽ khi sử dụng tốt phương pháp tiếp cận phương pháp được đặt tên cũ (hành động mới (MyMethod)). Họ không so sánh để được bình đẳng bởi vì các.NET Framework cung cấp một trường hợp đóng cửa ẩn cho mỗi trường hợp đại biểu. Vì hai cá thể đại biểu đó đều có thuộc tính Target được đặt cho cá thể ẩn riêng của chúng, nên chúng không so sánh. Một giải pháp có thể là cho IL được tạo ra cho một phương thức nặc danh để lưu trữ cá thể hiện tại (con trỏ này) trong đích của đại biểu. Điều này sẽ cho phép các đại biểu để so sánh chính xác, và cũng giúp từ một quan điểm trình gỡ lỗi vì bạn sẽ thấy lớp của bạn là mục tiêu, thay vì một lớp ẩn.

Bạn có thể đọc thêm về vấn đề này trong lỗi mà tôi đã gửi cho Microsoft. Báo cáo lỗi cũng đưa ra một ví dụ về lý do tại sao chúng tôi đang sử dụng chức năng này và tại sao chúng tôi cảm thấy nó nên được thay đổi. Nếu bạn cảm thấy đây cũng là vấn đề, vui lòng giúp hỗ trợ nó bằng cách cung cấp xếp hạng và xác thực.

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489518

Bạn có thể thấy bất kỳ lý do có thể lý do tại sao các chức năng không nên thay đổi? Bạn có cảm thấy đây là hành động tốt nhất để giải quyết vấn đề hay bạn khuyên tôi nên đi theo một tuyến đường khác?

+2

Trong đó đặc điểm kỹ thuật nào Microsoft đề cập rằng hai giá trị này phải bằng nhau? Với cùng một lý do, tôi có thể lập luận rằng 'new MyClass() == new MyClass()' phải là 'true' nếu' MyClass' là một lớp rỗng, chẳng hạn. Bạn quá nhanh để đặt tên cho một hành vi không được đề cập trong bất kỳ thông số nào là "lỗi". –

+0

Trong đó đặc điểm kỹ thuật nào họ đề cập rằng họ không nên bằng nhau? –

Trả lời

19

Tôi không quá nghiêng để nghĩ rằng đây là một "lỗi". Nó xuất hiện nhiều hơn rằng bạn đang giả định một số hành vi trong CLR mà chỉ đơn giản là không tồn tại.

Điều quan trọng cần hiểu ở đây là bạn đang trả về một phương pháp ẩn danh mới (và khởi tạo một lớp đóng mới) mỗi khi bạn gọi phương thức CreateDelegate. Có vẻ như bạn đang kiểm định từ khóa delegate để sử dụng một số loại hồ bơi cho các phương thức ẩn danh trong nội bộ. CLR chắc chắn không làm điều này. Một đại biểu cho phương thức ẩn danh (như với biểu thức lambda) được tạo ra trong bộ nhớ mỗi lần bạn gọi phương thức, và vì toán tử bình đẳng dĩ nhiên so sánh tham chiếu trong trường hợp này, kết quả mong đợi là trả về false.

Mặc dù hành vi được đề xuất của bạn có thể có một số lợi ích trong một số ngữ cảnh nhất định, có thể sẽ khá phức tạp để triển khai và có nhiều khả năng dẫn đến các tình huống khó lường. Tôi nghĩ rằng hành vi hiện tại của việc tạo ra một phương pháp vô danh mới và ủy nhiệm trên mỗi cuộc gọi là đúng, và tôi nghi ngờ đây là phản hồi bạn sẽ nhận được trên Microsoft Connect là tốt.

Nếu bạn khá kiên quyết về hành vi bạn mô tả trong câu hỏi, luôn có tùy chọn memoizing chức năng CreateDelegate của bạn, đảm bảo rằng cùng một đại biểu được trả lại mỗi lần cho cùng một tham số. Thật vậy, bởi vì điều này rất dễ thực hiện, nó có lẽ là một trong nhiều lý do tại sao Microsoft đã không xem xét việc thực hiện nó trong CLR.

+0

Biểu thức Lambda không liên quan gì đến điều này. Nhìn vào IL. Tôi không đề nghị bất kỳ loại hỗn hợp điên rồ nào hay bất cứ thứ gì thuộc loại này. Tôi chỉ đơn giản đề xuất rằng thực hiện sang một bên, làm thế nào bạn, như các nhà phát triển viết mã, muốn nó làm việc biết những gì bạn biết về so sánh của một đại biểu. Hãy quên đi các chi tiết thực hiện của các phương thức nặc danh. Tôi chỉ muốn thay đổi nó cho cộng đồng nhà phát triển và Khung công tác tốt hơn. –

+0

@BigUnit: Biểu thức Lambda có rất nhiều việc phải làm với điều này. Đó là chính xác bởi vì chúng được tạo ra trên bay mà bạn đang trải qua sự nhầm lẫn này. Cá nhân, tôi với các nhà phát triển .NET Framework. Nếu tôi viết mã được đưa ra trong câu hỏi trên, kết quả thực tế sẽ là kết quả mong đợi của tôi. Tất nhiên, người ta khó có thể phân loại hành vi 'trực giác' này, nhưng tôi nghĩ bạn sẽ thấy nó là hành vi hoàn toàn hợp lý khi bạn quấn đầu xung quanh các hoạt động bên trong của CLR. – Noldorin

+0

@Noldorin: Xin lỗi, tôi vẫn không thấy biểu thức lambda khớp ở đâu. Không có LE trong mã của tôi, cũng như trong IL sản xuất. Có chắc chắn không có thời gian chạy trên thế hệ bay, nếu đó là những gì bạn đang nhận được tại. –

4

Tôi không biết về C# chi tiết cụ thể của vấn đề này nhưng tôi đã làm việc trên các tính năng tương đương VB.Net có hành vi tương tự.

Điểm mấu chốt là hành vi này là "Bằng cách thiết kế" vì những lý do sau đây

Đầu tiên là trong kịch bản này đóng cửa là không thể tránh khỏi. Bạn đã sử dụng một đoạn dữ liệu cục bộ trong một phương thức ẩn danh và do đó việc đóng cửa là cần thiết để nắm bắt trạng thái. Mọi cuộc gọi đến phương thức này phải tạo một đóng mới vì một số lý do.Vì vậy, mỗi đại biểu sẽ trỏ đến một phương pháp thể hiện về việc đóng cửa đó.

Theo cách trình bày, một phương thức/biểu thức ẩn danh được thể hiện bằng một thể hiện có nguồn gốc System.MulticastDelegate trong mã. Nếu bạn nhìn vào Equals phương pháp của lớp này bạn sẽ thấy 2 chi tiết quan trọng

  • Nó được bịt kín nên không có cách nào cho một đại biểu xuất phát để thay đổi tương đương với hành vi
  • Một phần của phương pháp Equals hiện một tài liệu tham khảo so sánh trên các đối tượng

Điều này làm cho không thể cho 2 biểu thức lambda được gắn vào các bao đóng khác nhau để so sánh như bằng.

+0

Tôi đồng ý rằng hôm nay Khung làm cho nó không thể so sánh các phương pháp nặc danh. Chỉ vì chúng trỏ đến hai trường hợp khác nhau không có nghĩa là chúng không thể so sánh tương đương. Chắc chắn nó sẽ có nghĩa là một sự thay đổi, nhưng tôi chắc chắn sẽ không tuyên bố nó là không thể quá nhanh. –

+0

@BigUnit, tôi nói rằng không thể cho tình trạng hiện tại của BCL. Đồng thời tôi cũng đồng ý rằng hành vi của BCL là chính xác. Hai phương thức thể hiện trên hai đối tượng không bao giờ nên so sánh với nhau. – JaredPar

+0

@ JaredPar: Tôi hiểu những gì bạn đang nói, nhưng nó được trừu tượng ra khỏi bạn. Bạn quên đề cập đến rằng hai đối tượng được ẩn từ nhà phát triển. Thực tế nó nắm bắt nhà nước chỉ là để làm cho nó dễ dàng hơn trên các nhà phát triển. Hãy cho tôi một ví dụ điển hình về mã hiện tại có thể phá vỡ khỏi việc thay đổi chức năng này? Nó sucks không có công việc tốt xung quanh vấn đề này mà làm cho nó đơn giản cho người sử dụng API, ngoại trừ không sử dụng các phương pháp vô danh và bị buộc phải sử dụng các mô hình Func/hành động. Sau đó tôi buộc phải tạo cùng một số lớp chung trong tình huống của mình, khoảng 30. –

0

Tôi không thể nghĩ ra một tình huống mà tôi từng cần để làm điều đó. Nếu tôi cần phải so sánh các đại biểu tôi luôn luôn sử dụng tên đại biểu, nếu không một cái gì đó như thế này sẽ có thể:

MyObject.MyEvent += delegate { return x + y; }; 

MyObject.MyEvent -= delegate { return x + y; }; 

Ví dụ này là không lớn để chứng minh vấn đề này, nhưng tôi sẽ tưởng tượng rằng có thể có một tình huống mà cho phép điều này có thể phá vỡ mã hiện có được thiết kế với kỳ vọng rằng điều này không được phép.

Tôi chắc chắn có các chi tiết triển khai nội bộ cũng khiến ý tưởng này trở thành một ý tưởng tồi, nhưng tôi không biết chính xác cách thức các phương thức ẩn danh được triển khai bên trong.

+0

Không ai làm như vậy vì kết quả thường là sai. Nhưng đây là trường hợp Khung cho phép kết quả như vậy: Nếu tôi có một phương pháp như sau: Hành động công khai CreateDelegate() { đại biểu trả lại {CallSomeMethod(); }; } Và sau đó sử dụng chức năng đó cho hookup/sự kiện của tôi: MyEvent + = CreateDelegate(); MyEvent - = CreateDelegate(); Hoạt động mà không có sự cố. Ngay cả hiện tại cũng có những trường hợp chúng sẽ cho phép chúng tương đương nhau. Vì vậy, không có cách nào để viết mã để xác định sự tương đương ngày hôm nay. –

3

EDIT: Cũ câu trả lời để lại cho giá trị lịch sử dưới dòng ...

Các CLR sẽ phải làm việc ra các trường hợp trong đó các lớp ẩn có thể được coi là bình đẳng, có tính đến bất cứ điều gì tài khoản có thể được thực hiện với các biến bị bắt.

Trong trường hợp cụ thể này, biến bị bắt (x) không được thay đổi trong đại biểu hoặc trong ngữ cảnh chụp - nhưng tôi muốn ngôn ngữ không yêu cầu loại phân tích phức tạp này. Ngôn ngữ càng phức tạp thì khó hiểu hơn. Nó sẽ phải phân biệt giữa trường hợp này và trường hợp bên dưới, trong đó giá trị của biến bị bắt được thay đổi trên mỗi lời gọi - ở đó, nó tạo ra rất nhiều sự khác biệt mà bạn ủy quyền gọi; chúng không bằng nhau.

Tôi nghĩ hoàn toàn hợp lý rằng tình huống đã phức tạp này (việc đóng cửa thường bị hiểu lầm) không cố gắng quá "thông minh" và tạo ra sự bình đẳng tiềm năng.

IMO, bạn nên chắc chắn có tuyến đường khác. Đây là các trường hợp độc lập độc lập của Action. Giả mạo nó bằng cách ép các mục tiêu đại biểu là một IMO hack khủng khiếp.


Vấn đề là bạn đang ghi lại giá trị x trong lớp được tạo. Hai biến số x độc lập, vì vậy chúng là những đại biểu không công bằng.Dưới đây là một ví dụ chứng minh sự độc lập:

using System; 

class Test 
{ 
    static void Main(string[] args) 
    { 
     Action first = CreateDelegate(1); 
     Action second = CreateDelegate(1); 
     first(); 
     first(); 
     first(); 
     first(); 
     second(); 
     second(); 
    } 

    private static Action CreateDelegate(int x) 
    { 
     return delegate 
     { 
      Console.WriteLine(x); 
      x++; 
     }; 
    } 
} 

Output:

1 
2 
3 
4 
1 
2 

EDIT: Để nhìn vào nó một cách khác, chương trình ban đầu của bạn là tương đương với:

using System; 

class Test 
{ 
    static void Main(string[] args) 
    { 
     bool same = CreateDelegate(1) == CreateDelegate(1); 
    } 

    private static Action CreateDelegate(int x) 
    { 
     return new NestedClass(x).ActionMethod; 
    } 

    private class Nested 
    { 
     private int x; 

     internal Nested(int x) 
     { 
      this.x = x; 
     } 

     internal ActionMethod() 
     { 
      int z = x; 
     } 
    } 
} 

Như bạn có thể cho biết, hai trường hợp riêng biệt của Nested sẽ được tạo và chúng sẽ là mục tiêu cho hai đại biểu. Họ là bất bình đẳng, do đó, các đại biểu là bất bình đẳng quá.

+0

Tôi nghĩ rằng OP hiểu điều này (đủ tốt), và đúng hơn là câu hỏi chính của ông liên quan đến * tại sao * đây là hành vi của CLR. – Noldorin

+0

@Noldorin: Yup, đã đọc lại câu hỏi và đọc lại. –

+0

Tôi hiểu tại sao nó hiện đang làm những gì nó làm. Bạn nói, "Ngôn ngữ phức tạp hơn, khó hiểu hơn." Tuy nhiên, tôi không đề xuất bất kỳ sự thay đổi nào đối với ngôn ngữ, nhưng đối với IL được tạo cơ bản đã được thực hiện thông qua trình biên dịch. Khung theo ý nghĩa được thực hiện để làm cho cuộc sống dễ dàng hơn, vậy tại sao làm cho nó khó khăn hơn khi chúng ta không phải làm vậy? Thay đổi Target không phải là câu trả lời hay nhất, cần phải có một cách tốt hơn. Nó có thể lưu trữ nó như là một lĩnh vực khác nhau trong các đại biểu, hoặc lớp chụp có thể lưu trữ nó trong một cách mà các đại biểu có thể truy cập để so sánh. –

0

Hành vi này có ý nghĩa bởi vì các phương pháp ẩn danh khác sẽ bị lẫn lộn (nếu chúng có cùng tên, cho cùng một nội dung).

Bạn có thể thay đổi mã của bạn như thế này:

static void Main(){ 
    bool same = CreateDelegate(1) == CreateDelegate(1); 
} 

static Action<int> action = (x) => { int z = x; }; 

private static Action<int> CreateDelegate(int x){ 
    return action; 
} 

Hoặc, tốt nhất, vì đó là một cách xấu để sử dụng nó (cộng với bạn đã so sánh kết quả, và hành động không có giá trị trả về .. . sử dụng Func < ...> nếu bạn muốn trả về một giá trị):

static void Main(){ 
    var action1 = action; 
    var action2 = action; 
    bool same = action1 == action2; // TRUE, of course 
} 

static Action<int> action = (x) => { int z = x; };