阻止事件处理程序钩住两次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; } } 

使用上面的代码,如果调用者多次订阅该事件,它将被简单地忽略。

如果只是首先用-=去除事件,如果没有发现exception则不会抛出

 /// -= 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); 

我已经testing了每个解决scheme,最好的(考虑性能)是:

 private EventHandler _foo; public event EventHandler Foo { add { _foo -= value; _foo += value; } remove { _foo -= value; } } 

没有Linq使用所需的。 在取消订阅之前不需要检查null(有关详细信息,请参阅MS EventHandler)。 无需记得到处取消订阅。

你真的应该在汇水平而不是水平来处理这个问题。 也就是说,不要在事件源处规定事件处理程序逻辑 – 将其留给处理程序(接收器)本身。

作为一个服务的开发者,你是谁说汇只能注册一次? 如果他们因为某种原因想要注册两次呢? 如果您正在尝试通过修改源来纠正汇点中的错误,那么在汇点级别纠正这些问题也是一个很好的理由。

我相信你有你的理由; 一个事件源非法重复汇是不可估量的。 但也许你应该考虑一个替代的架构,使事件的语义完好无损。

您需要在事件上实现添加和删除访问器,然后检查委托的目标列表,或将目标存储在列表中。

在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 

(即只有一次)

微软的Reactive Extensions(Rx)框架也可以用来“只订阅一次”。

给定一个鼠标事件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; } 

除了提供“订阅一次”function之外,RX方法还可以组合事件或过滤事件。 这很漂亮。

创build一个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); } } 

让你的单身对象检查它是谁通知的列表,只有重复时调用一次。 或者,如果可能拒绝事件附件请求。

在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); } 

如果这有帮助,请勾选我。

也许我对类似post的回答将有所帮助:

区分用户交互引发的事件和我自己的代码