2009-06-01 23 views
73

Duplicate của: How to ensure an event is only subscribed to onceHas an event handler already been added?C# mô hình để ngăn chặn một event handler nối hai lần

Tôi có một singleton mà cung cấp một số dịch vụ và các lớp học của tôi móc vào một số sự kiện trên đó, đôi khi một lớp được hooking hai lần để sự kiện này và sau đó được gọi hai lần. Tôi đang tìm cách cổ điển để ngăn điều này xảy ra. bằng cách nào đó, tôi cần phải kiểm tra xem tôi đã kết nối với sự kiện này chưa ...

Trả lời

120

Triển khai rõ ràng sự kiện và kiểm tra danh sách yêu cầu. Bạn cũng sẽ cần phải kiểm tra null:

using System.Linq; // Required for the .Contains call below: 

... 

private EventHandler foo; 
public event EventHandler Foo 
{ 
    add 
    { 
     if (foo == null || !foo.GetInvocationList().Contains(value)) 
     { 
      foo += value; 
     } 
    } 
    remove 
    { 
     foo -= value; 
    } 
} 

Sử dụng mã ở trên, nếu người gọi đăng ký sự kiện nhiều lần, nó sẽ bị bỏ qua.

+13

Bạn cần sử dụng System.Linq bằng cách sử dụng. –

+0

Chỉ để làm rõ nhận xét của Hermann; bạn phải bao gồm không gian tên 'System.Linq' bằng cách thêm 'using System.Linq' vào lớp của bạn hoặc không gian tên hiện tại. –

+0

Hấp dẫn. LINQ vẫn còn đủ mới với tôi rằng tôi phải tìm kiếm nó và được nhắc nhở nó có nghĩa là Ngôn ngữ truy vấn tích hợp ... và sau đó tự hỏi những gì đã làm với EventHandlers và InvocationList của họ? – fortboise

12

Bạn cần triển khai thêm và xóa người truy cập trên sự kiện, sau đó kiểm tra danh sách đích của đại biểu hoặc lưu trữ các mục tiêu trong danh sách.

Trong phương thức thêm, bạn có thể sử dụng phương thức Delegate.GetInvocationList để nhận danh sách các mục tiêu đã được thêm vào đại biểu.

Vì các đại biểu được xác định để so sánh nếu chúng được liên kết với cùng một phương pháp trên cùng một đối tượng đích, bạn có thể chạy qua danh sách đó và so sánh, và nếu bạn thấy không có gì so sánh bằng nhau, bạn thêm mới .

Dưới đây là mẫu mã, biên dịch như giao diện điều khiển ứng dụng:

using System; 
using System.Linq; 

namespace DemoApp 
{ 
    public class TestClass 
    { 
     private EventHandler _Test; 

     public event EventHandler Test 
     { 
      add 
      { 
       if (_Test == null || !_Test.GetInvocationList().Contains(value)) 
        _Test += value; 
      } 

      remove 
      { 
       _Test -= value; 
      } 
     } 

     public void OnTest() 
     { 
      if (_Test != null) 
       _Test(this, EventArgs.Empty); 
     } 
    } 

    class Program 
    { 
     static void Main() 
     { 
      TestClass tc = new TestClass(); 
      tc.Test += tc_Test; 
      tc.Test += tc_Test; 
      tc.OnTest(); 
      Console.In.ReadLine(); 
     } 

     static void tc_Test(object sender, EventArgs e) 
     { 
      Console.Out.WriteLine("tc_Test called"); 
     } 
    } 
} 

Output:

tc_Test called 

(tức là chỉ một lần.)

+0

Vui lòng bỏ qua nhận xét của tôi, tôi đã quên LINQ bằng cách sử dụng. –

+0

Giải pháp sạch nhất (mặc dù không phải là ngắn nhất). – Shimmy

0

có đối tượng singleton bạn kiểm tra xem nó là danh sách của người đó thông báo và chỉ gọi một lần nếu bị trùng lặp. Hoặc nếu có thể từ chối yêu cầu đính kèm sự kiện.

17

Bạn thực sự nên xử lý này ở cấp bồn rửa và không cấp nguồn. Đó là, không quy định logic xử lý sự kiện tại nguồn sự kiện - để điều đó cho người xử lý (bồn).

Là nhà phát triển dịch vụ, bạn là ai để nói rằng bồn rửa chỉ có thể đăng ký một lần? Nếu họ muốn đăng ký hai lần vì một lý do nào đó thì sao? Và nếu bạn đang cố gắng sửa lỗi trong bồn bằng cách sửa đổi nguồn, nó lại là một lý do chính đáng để sửa chữa những vấn đề này ở cấp độ chìm.

Tôi chắc chắn bạn có lý do; một nguồn sự kiện mà các bồn rửa trùng lặp bất hợp pháp không phải là không thể dò được. Nhưng có lẽ bạn nên xem xét một kiến ​​trúc thay thế mà rời khỏi ngữ nghĩa của một sự kiện còn nguyên vẹn.

+0

Đây là một giải pháp kỹ thuật tuyệt vời cho [giải pháp này] (http://stackoverflow.com/a/1104269/3367144), giải quyết vấn đề về sự kiện chìm (thuê bao/người tiêu dùng/quan sát/xử lý) bên thay vì nguồn bên. – kdbanman

140

Làm thế nào về chỉ cần loại bỏ sự kiện đầu tiên với -=, nếu nó không được tìm thấy một ngoại lệ không được ném

/// -= Removes the event if it has been already added, this prevents multiple firing of the event 
((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii); 
((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii); 
+0

Cảm ơn bạn. Điều này rất tiện dụng trong cùng một kịch bản (WebBrowser + HtmlElementEventHandler). Cảm ơn bạn đã chỉ ra điều này – Odys

+0

Đây phải là câu trả lời được chấp nhận vì nó đơn giản và không yêu cầu triển khai tùy chỉnh. @LoxLox hiển thị cùng một mẫu như một quá trình triển khai. Tôi đã không kiểm tra, vì vậy tôi lấy những lời nhận xét theo lời của họ. Rất đẹp. – Rafe

+3

+1 Tôi đoán nó là lập trình viên, nhưng tôi sẽ nói đây là tốt nhất (không phải là "gọn gàng" nhất để yêu cầu nhà phát triển cuối thực hiện điều này, nhưng nó không phải là lỗi của người tạo sự kiện mà người đăng ký không thể ngăn nhiều đăng ký, vì vậy, làm cho chúng tìm ra loại bỏ, v.v ... bên cạnh đó, tại sao ngăn người khác đăng ký cùng một trình xử lý nhiều lần nếu muốn?) –

6

của Reactive Extensions (Rx) framework Microsoft cũng có thể được sử dụng để làm "đăng ký một lần duy nhất".

Cho một sự kiện chuột foo.Clicked, dưới đây là cách để đăng ký và nhận được chỉ là một lời kêu cầu duy nhất:

Observable.FromEvent<MouseEventArgs>(foo, "Clicked") 
    .Take(1) 
    .Subscribe(MyHandler); 

... 

private void MyHandler(IEvent<MouseEventArgs> eventInfo) 
{ 
    // This will be called just once! 
    var sender = eventInfo.Sender; 
    var args = eventInfo.EventArgs; 
} 

Ngoài việc cung cấp "đăng ký một lần" chức năng, cách tiếp cận RX cung cấp khả năng soạn các sự kiện lại với nhau hoặc lọc sự kiện. Nó khá tiện lợi.

+0

Mặc dù điều này là đúng về mặt kỹ thuật, nó trả lời câu hỏi sai. – AlexFoxGill

0

Trong silverlight, bạn cần phải nói e.Handled = true; trong mã sự kiện.

void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
{ 
    e.Handled = true; //this fixes the double event fire problem. 
    string name = (e.OriginalSource as Image).Tag.ToString(); 
    DoSomething(name); 
} 

Vui lòng chọn tôi nếu điều này có ích.

1

Tạo hành động thay vì sự kiện. lớp học của bạn có thể trông giống như:

public class MyClass 
{ 
       // sender arguments  <-----  Use this action instead of an event 
    public Action<object, EventArgs> OnSomeEventOccured; 

    public void SomeMethod() 
    { 
      if(OnSomeEventOccured!=null) 
       OnSomeEventOccured(this, null); 
    } 

} 
20

Tôi đã thử nghiệm mỗi giải pháp và một (xem xét hiệu suất) tốt nhất là:

private EventHandler _foo; 
public event EventHandler Foo { 

    add { 
     _foo -= value; 
     _foo += value; 
    } 
    remove { 
     _foo -= value; 
    } 
} 

Không LINQ sử dụng yêu cầu. Không cần kiểm tra null trước khi hủy đăng ký (xem MS EventHandler để biết chi tiết). Không cần phải nhớ để bỏ đăng ký ở khắp mọi nơi.

Các vấn đề liên quan