2009-07-13 7 views
25

Trong khi điều tra this question Tôi đã tò mò về cách tính hiệp phương sai/đối xứng mới trong C# 4.0 sẽ ảnh hưởng đến nó.Sự kiện và ủy quyền đối nghịch trong .NET 4.0 và C# 4.0

Trong bản Beta 1, C# dường như không đồng ý với CLR. Sao lưu trong C# 3.0, nếu bạn có:

public event EventHandler<ClickEventArgs> Click; 

... và sau đó ở nơi khác bạn có:

button.Click += new EventHandler<EventArgs>(button_Click); 

... trình biên dịch sẽ barf vì chúng là hai loại đại biểu không tương thích. Nhưng trong C# 4.0, nó biên dịch tốt, bởi vì trong CLR 4.0 tham số kiểu bây giờ được đánh dấu là in, do đó, nó là contravariant, và do đó trình biên dịch giả định đại biểu multicast += sẽ hoạt động.

Dưới đây là thử nghiệm của tôi:

public class ClickEventArgs : EventArgs { } 

public class Button 
{ 
    public event EventHandler<ClickEventArgs> Click; 

    public void MouseDown() 
    { 
     Click(this, new ClickEventArgs()); 
    } 
} 

class Program 
{  
    static void Main(string[] args) 
    { 
     Button button = new Button(); 

     button.Click += new EventHandler<ClickEventArgs>(button_Click); 
     button.Click += new EventHandler<EventArgs>(button_Click); 

     button.MouseDown(); 
    } 

    static void button_Click(object s, EventArgs e) 
    { 
     Console.WriteLine("Button was clicked"); 
    } 
} 

Nhưng mặc dù nó biên dịch, nó không hoạt động trong thời gian chạy (ArgumentException: Các đại biểu phải có cùng loại).

Sẽ ổn nếu bạn chỉ thêm một trong hai loại đại biểu. Nhưng sự kết hợp của hai loại khác nhau trong một multicast gây ra ngoại lệ khi thứ hai được thêm vào.

Tôi đoán đây là lỗi trong CLR trong bản beta 1 (hành vi của trình biên dịch có vẻ hy vọng đúng).

Cập nhật cho Release Candidate:

Đoạn mã trên không còn biên dịch. Nó phải được rằng các contravariance của TEventArgs trong loại EventHandler<TEventArgs> đại biểu đã được cuộn lại, do đó, bây giờ mà đại biểu có định nghĩa tương tự như trong .NET 3.5.

Đó là, các phiên bản beta Tôi nhìn phải có:

public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e); 

Bây giờ nó trở lại:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e); 

Nhưng tham số Action<T> đại biểu T vẫn contravariant:

public delegate void Action<in T>(T obj); 

Tương tự với Func<T> 's T là biến thể.

Thỏa hiệp này có ý nghĩa rất nhiều, miễn là chúng tôi giả định rằng việc sử dụng chính các đại biểu đa phương tiện là trong ngữ cảnh của các sự kiện. Cá nhân tôi thấy rằng tôi không bao giờ sử dụng các đại biểu đa phương tiện ngoại trừ các sự kiện.

Vì vậy, tôi đoán các tiêu chuẩn mã hóa C# giờ đây có thể áp dụng quy tắc mới: không tạo thành đại biểu đa phương tiện từ nhiều loại đại biểu có liên quan thông qua hiệp phương sai/contravariance. Và nếu bạn không biết điều đó có nghĩa là gì, chỉ cần tránh sử dụng Action để các sự kiện ở bên an toàn.

Tất nhiên, kết luận rằng có ý nghĩa đối với the original question that this one grew from ...

+1

Thắc mắc thú vị của bạn muốn là câu hỏi? –

+0

Tôi ngạc nhiên lỗ này thoát khỏi thông báo của đội C#, nên là một trong những điều đầu tiên họ sẽ thử nghiệm sau khi giới thiệu phương sai cho các đại biểu chung không phải là nó? C# 5 cũng trưng bày nó (phiên bản clr giống nhau). – nawfal

Trả lời

0

Bạn nhận được ArgumentException từ cả hai?Nếu ngoại lệ được ném bởi chỉ là trình xử lý mới, thì tôi nghĩ rằng nó sẽ tương thích ngược.

BTW, tôi nghĩ rằng bạn đã nhận xét của bạn được trộn lẫn. Trong C# 3.0 này:

button.Click += new EventHandler<EventArgs>(button_Click); // old

sẽ không chạy. Đó là C# 4.0

+0

Tôi đã xóa tất cả các tham chiếu đến phiên bản trong câu hỏi, vì dường như nó đã khiến bạn nhầm lẫn. Các ý kiến ​​về mới và cũ liên quan đến vấn đề tôi đã suy nghĩ ban đầu, mà tôi chia câu hỏi này ra. Câu hỏi này không liên quan gì đến khả năng tương thích ngược.Đó là về trình biên dịch giả định rằng một đại biểu multicast có thể liên kết với các phương thức của các loại contravariant, sau đó hóa ra không đúng khi chạy. –

9

Rất thú vị. Bạn không cần phải sử dụng các sự kiện để xem điều này xảy ra, và thực sự tôi thấy nó đơn giản hơn để sử dụng các đại biểu đơn giản.

Cân nhắc Func<string>Func<object>. Trong C# 4.0, bạn có thể chuyển đổi hoàn toàn một số Func<string> thành Func<object> vì bạn luôn có thể sử dụng tham chiếu chuỗi làm tham chiếu đối tượng. Tuy nhiên, mọi thứ xảy ra khi bạn cố gắng kết hợp chúng. Dưới đây là một chương trình ngắn nhưng đầy đủ chứng minh vấn đề theo hai cách khác nhau:

using System; 

class Program 
{  
    static void Main(string[] args) 
    { 
     Func<string> stringFactory =() => "hello"; 
     Func<object> objectFactory =() => new object(); 

     Func<object> multi1 = stringFactory; 
     multi1 += objectFactory; 

     Func<object> multi2 = objectFactory; 
     multi2 += stringFactory; 
    }  
} 

này biên dịch tốt, nhưng cả hai Combine cuộc gọi (ẩn bởi + = cú pháp đường) ném ngoại lệ. (Bình luận ra cái đầu tiên để xem cái thứ hai.)

Đây chắc chắn là một vấn đề, mặc dù tôi không chắc chắn chính xác giải pháp nên là gì. Có thể là tại thời điểm thực hiện mã ủy quyền sẽ cần phải tìm ra loại phù hợp nhất để sử dụng dựa trên các loại đại biểu có liên quan. Đó là một chút khó chịu. Sẽ rất tuyệt khi có cuộc gọi chung Delegate.Combine, nhưng bạn không thể thực sự thể hiện các loại liên quan theo cách có ý nghĩa.

Một điều đó là đáng chú ý là việc chuyển đổi hiệp biến là chuyển đổi tài liệu tham khảo - ở trên, multi1stringFactory tham khảo cùng một đối tượng: đó là không giống như viết

Func<object> multi1 = new Func<object>(stringFactory); 

(Lúc đó điểm, dòng sau sẽ thực thi mà không có ngoại lệ.) Vào thời gian thực hiện, BCL thực sự phải xử lý một số Func<string>Func<object> được kết hợp; nó không có thông tin khác để tiếp tục.

Thật khó chịu và tôi thực sự hy vọng nó sẽ được khắc phục theo một cách nào đó. Tôi sẽ cảnh báo với Mads và Eric về câu hỏi này để chúng tôi có thể nhận được một số bình luận chi tiết hơn.

+0

Thật tuyệt, tôi đã đến cùng một đoạn mã mẫu giống nhau khi mày mò với nó trên tàu về nhà. Sẽ rất thú vị khi nghe những chi tiết gnarly. –

1

Tôi vừa sửa lỗi này trong ứng dụng của mình. Tôi đã làm như sau:

// variant delegate with variant event args 
MyEventHandler<<in T>(object sender, IMyEventArgs<T> a) 

// class implementing variant interface 
class FiresEvents<T> : IFiresEvents<T> 
{ 
    // list instead of event 
    private readonly List<MyEventHandler<T>> happened = new List<MyEventHandler<T>>(); 

    // custom event implementation 
    public event MyEventHandler<T> Happened 
    { 
     add 
     { 
      happened.Add(value); 
     } 
     remove 
     { 
      happened.Remove(value); 
     } 
    } 

    public void Foo() 
    { 
     happened.ForEach(x => x.Invoke(this, new MyEventArgs<T>(t)); 
    } 
} 

Tôi không biết nếu có sự khác biệt có liên quan đến sự kiện nhiều diễn viên thông thường. Theo như tôi đã sử dụng, nó hoạt động ...

Nhân tiện: I never liked the events in C#. Tôi không hiểu tại sao có một tính năng ngôn ngữ, khi nó không cung cấp bất kỳ lợi thế nào.

+1

Sự khác biệt chính là các đại biểu multicast là không thay đổi, do đó, nó là an toàn thread để thêm/gỡ bỏ các trình xử lý trong khi gọi. Trong triển khai của bạn, danh sách có thể thay đổi trong khi gọi sự kiện với kết quả không thể đoán trước. –

+0

@ SørenBoisen: rất tốt. Cảm ơn bạn vì điều này. Tôi có thể sử dụng 'ConcurrentBag ' thay vì danh sách. –

+1

Một tùy chọn khác là sử dụng ImmutableArray hoặc ImmutableList từ http://blogs.msdn.com/b/dotnet/archive/2013/09/25/immutable-collections-ready-for-prime-time.aspx. Điều đó sẽ tối ưu hóa cho công văn vs thêm/loại bỏ, nhưng quan trọng hơn, chúng có sẵn trong các dự án PCL :-) –