防止事件处理程序挂钩两次的 C # 模式

如何确保一个事件只订阅一次 和 < a href = “ https://stackoverflow. com/questions/136975/Has-an-event-handle-already-been-add”> 是否已经添加了事件处理程序?

我有一个单例模式,它提供了一些服务,我的类与它上面的一些事件挂钩,有时一个类挂钩两次事件,然后被调用两次。 我正在寻找一种经典的方法来防止这种情况的发生。不知道为什么,我需要检查一下我是否已经被这个活动吸引住了。

101748 次浏览

You need to implement the add and remove accessors on the event, and then check the target list of the delegate, or store the targets in a list.

In the add method, you can use the Delegate.GetInvocationList method to obtain a list of the targets already added to the delegate.

Since delegates are defined to compare equal if they're linked to the same method on the same target object, you could probably run through that list and compare, and if you find none that compares equal, you add the new one.

Here's sample code, compile as console application:

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

(ie. only once)

have your singleton object check it's list of who it notifies and only call once if duplicated. Alternatively if possible reject event attachment request.

Explicitly implement the event and check the invocation list. You'll also need to check for 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;
}
}

Using the code above, if a caller subscribes to the event multiple times, it will simply be ignored.

You really should handle this at the sink level and not the source level. That is, don't prescribe event handler logic at the event source - leave that to the handlers (the sinks) themselves.

As the developer of a service, who are you to say that sinks can only register once? What if they want to register twice for some reason? And if you are trying to correct bugs in the sinks by modifying the source, it's again a good reason for correcting these issues at the sink-level.

I'm sure you have your reasons; an event source for which duplicate sinks are illegal is not unfathomable. But perhaps you should consider an alternate architecture that leaves the semantics of an event intact.

How about just removing the event first with -= , if it is not found an exception is not thrown

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

Microsoft's Reactive Extensions (Rx) framework can also be used to do "subscribe only once".

Given a mouse event foo.Clicked, here's how to subscribe and receive only a single invocation:

Observable.FromEvent<MouseEventArgs>(foo, nameof(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;
}

In addition to providing "subscribe once" functionality, the RX approach offers the ability to compose events together or filter events. It's quite nifty.

In silverlight you need to say e.Handled = true; in the event code.

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

Please tick me if this helps.

Create an Action instead of an event. Your class may look like:

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


}

I've tested each solution and the best one (considering performance) is:

private EventHandler _foo;
public event EventHandler Foo {


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

No Linq using required. No need to check for null before cancelling a subscription (see MS EventHandler for details). No need to remember to do the unsubscription everywhere.