重複:How to ensure an event is only subscribed to once 和Has an event handler already been added?C#模式,以防止事件處理程序掛接的兩倍
我有一單,提供一些服務,我的課掛接到它的一些事件,有時一類是掛鉤兩次事件然後被調用兩次。 我正在尋找一種經典的方法來防止這種情況發生。不知何故,我需要檢查,如果我已經迷上這個事件......
重複:How to ensure an event is only subscribed to once 和Has an event handler already been added?C#模式,以防止事件處理程序掛接的兩倍
我有一單,提供一些服務,我的課掛接到它的一些事件,有時一類是掛鉤兩次事件然後被調用兩次。 我正在尋找一種經典的方法來防止這種情況發生。不知何故,我需要檢查,如果我已經迷上這個事件......
明確實現的情況下,檢查調用列表。您還需要檢查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;
}
}
使用上面的代碼,如果調用訂閱事件多次,它只會被忽略。
您需要實現的增加和對事件remove訪問,然後檢查委託的目標列表,或者目標存儲在名單。
在add方法,您可以使用Delegate.GetInvocationList方法來獲取已添加到代表目標的清單。
因爲如果代表被定義爲在相同目標對象上鍊接到相同方法時比較相等,則可以運行該列表並進行比較,如果找不到比較相等的方法,則添加新代碼。
這裏的示例代碼,編譯爲控制檯應用程序:
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");
}
}
}
輸出:
tc_Test called
(即只有一次。)
請忽略我的評論,我忘記了Linq使用。 – 2009-06-18 17:55:11
最乾淨的解決方案(儘管不是最短的)。 – Shimmy 2017-09-19 07:14:41
有你單身的對象檢查它的它會通知誰名單如果重複只能調用一次。或者,如果可能拒絕事件附件請求。
你真的應該在水槽的水平,而不是源代碼級的處理這個問題。也就是說,不要在事件源處規定事件處理程序邏輯 - 將其留給處理程序(接收器)本身。
作爲服務的開發者,誰是你說匯只能註冊一次?如果他們因某種原因想要註冊兩次會怎樣?如果您試圖通過修改源來糾正匯點中的錯誤,那麼在匯點級別糾正這些問題也是一個很好的理由。
我敢肯定,你有你的理由;一個事件源非法重複匯是不可估量的。但是也許你應該考慮一個可以保持事件語義完整的備用架構。
如何只用-=
先刪除事件,如果沒有發現這是一個異常沒有拋出
/// -= 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);
微軟的Reactive Extensions (Rx) framework也可以用來做「訂閱只有一次」。
給出一個鼠標事件foo.Clicked,這裏是如何訂閱和接收只有一個調用:
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;
}
除了提供「訂閱一次」功能,在RX方法提供撰寫一起事件的能力或過濾事件。這非常漂亮。
在silverlight中你需要說e.Handled = true;在事件代碼中。
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);
}
如果有幫助,請打勾我。
創建一個Action而不是一個事件。你的類可能看起來像:
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);
}
}
我測試過的每解決方案,最好的一個(考慮性能)是:
private EventHandler _foo;
public event EventHandler Foo {
add {
_foo -= value;
_foo += value;
}
remove {
_foo -= value;
}
}
沒有使用Linq的需要。在取消訂閱之前不需要檢查null(有關詳細信息,請參閱MS EventHandler)。無需記得到處取消訂閱。
您需要使用System.Linq。 – 2009-06-18 17:55:51
只是爲了澄清赫爾曼的評論;您必須通過將'使用System.Linq'添加到您的類或當前名稱空間來包含命名空間'System.Linq'。 – 2010-08-13 11:34:11
迷人。 LINQ對我來說仍然是新的,我必須查看它,並提醒它它意味着Language Integrated Query ...,然後想知道與EventHandlers及其InvocationList有什麼關係? – fortboise 2011-11-11 20:54:07