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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/937181/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 03:43:58  来源:igfitidea点击:

C# pattern to prevent an event handler hooked twice

c#event-handlingdelegates

提问by Ali Shafai

Duplicate of: How to ensure an event is only subscribed to onceand Has an event handler already been added?

重复:如何确保事件仅订阅一次并且是否已添加事件处理程序?

I have a singleton that provides some service and my classes hook into some events on it, sometimes a class is hooking twice to the event and then gets called twice. I'm looking for a classical way to prevent this from happening. somehow I need to check if I've already hooked to this event...

我有一个提供一些服务的单例,我的类挂钩到它上面的一些事件,有时一个类挂钩两次事件然后被调用两次。我正在寻找一种经典的方法来防止这种情况发生。不知何故我需要检查我是否已经迷上了这个事件......

采纳答案by Judah Gabriel Himango

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.

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

回答by Lasse V. Karlsen

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.GetInvocationListmethod to obtain a list of the targets already added to the delegate.

在 add 方法中,您可以使用Delegate.GetInvocationList方法获取已添加到委托中的目标列表。

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)

(即只有一次)

回答by Toby Allen

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.

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

回答by JP Alioto

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.

作为一个服务的开发者,谁说sinks只能注册一次?如果他们出于某种原因想注册两次怎么办?如果您试图通过修改源来纠正接收器中的错误,这又是在接收器级别纠正这些问题的一个很好的理由。

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.

我相信你有你的理由;重复接收器是非法的事件源并非深不可测。但也许您应该考虑采用一种替代架构,使事件的语义完好无损。

回答by PrimeTSS

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

回答by Judah Gabriel Himango

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

Microsoft 的Reactive Extensions (Rx) 框架也可用于“仅订阅一次”。

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

给定一个鼠标事件 foo.Clicked,这里是如何订阅和只接收一个调用:

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.

除了提供“订阅一次”功能之外,RX 方法还提供将事件组合在一起或过滤事件的能力。它很漂亮。

回答by Omzig

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

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

Please tick me if this helps.

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

回答by Tono Nam

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

}

回答by LoxLox

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.

无需使用 Linq。取消订阅之前无需检查 null(有关详细信息,请参阅 MS EventHandler)。无需记住在任何地方取消订阅。