重复:How to ensure an event is only subscribed to once 和Has an event handler already been added?
我有一个提供一些服务的单例,我的类挂钩到它上面的一些事件,有时一个类挂起两次事件然后被调用两次。 我正在寻找一种经典的方法来防止这种情况发生。不知怎的,我需要检查一下我是否已经迷上了这个事件......
答案 0 :(得分:161)
如果仅使用-=
删除事件,如果未找到,则不会抛出异常
/// -= 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);
答案 1 :(得分:139)
显式实现事件并检查调用列表。您还需要检查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;
}
}
使用上面的代码,如果调用者多次订阅该事件,它将被忽略。
答案 2 :(得分:29)
我已经测试了每个解决方案,最好的解决方案(考虑性能)是:
private EventHandler _foo;
public event EventHandler Foo {
add {
_foo -= value;
_foo += value;
}
remove {
_foo -= value;
}
}
没有Linq使用必需。在取消订阅之前无需检查null(有关详细信息,请参阅MS EventHandler)。无需记住在任何地方都取消订阅。
答案 3 :(得分:19)
你真的应该在接收器级而不是源级别处理这个问题。也就是说,不要在事件源处规定事件处理程序逻辑 - 将其留给处理程序(接收器)本身。
作为服务的开发者,你是说谁只能注册一次?如果他们出于某种原因想要注册两次怎么办?如果你试图通过修改源来纠正接收器中的错误,那么这也是在接收器级纠正这些问题的一个很好的理由。
我确定你有理由;重复接收器非法的事件源并不是不可思议的。但也许您应该考虑一种替代架构,使事件的语义保持不变。
答案 4 :(得分:14)
您需要在事件上实现添加和删除访问器,然后检查委托的目标列表,或将目标存储在列表中。
在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
(即只有一次)
答案 5 :(得分:6)
Microsoft的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方法还可以将事件组合在一起或过滤事件。这很漂亮。
答案 6 :(得分:2)
创建一个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);
}
}
答案 7 :(得分:0)
让你的单例对象检查它通知的列表,如果重复则只调用一次。或者,如果可能,拒绝事件附件请求。
答案 8 :(得分:0)
在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);
}
如果这有帮助,请打勾我。