如何确保一个事件只订阅一次

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/367523/
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-04 00:45:07  来源:igfitidea点击:

How to ensure an event is only subscribed to once

c#eventsevent-handlingsubscription

提问by Glen T

I would like to ensure that I only subscribe once in a particular class for an event on an instance.

我想确保我只在特定类中为实例上的事件订阅一次。

For example I would like to be able to do the following:

例如,我希望能够执行以下操作:

if (*not already subscribed*)
{
    member.Event += new MemeberClass.Delegate(handler);
}

How would I go about implementing such a guard?

我将如何实施这样的警卫?

采纳答案by Hamish Smith

If you are talking about an event on a class that you have access to the source for then you could place the guard in the event definition.

如果您正在谈论您可以访问其源的类上的事件,那么您可以将守卫放在事件定义中。

private bool _eventHasSubscribers = false;
private EventHandler<MyDelegateType> _myEvent;

public event EventHandler<MyDelegateType> MyEvent
{
   add 
   {
      if (_myEvent == null)
      {
         _myEvent += value;
      }
   }
   remove
   {
      _myEvent -= value;
   }
}

That would ensure that only one subscriber can subscribe to the event on this instance of the class that provides the event.

这将确保只有一个订阅者可以订阅提供事件的类的此实例上的事件。

EDITplease see comments about why the above code is a bad idea and not thread safe.

编辑请查看有关为什么上述代码是一个坏主意而不是线程安全的评论。

If your problem is that a single instance of the client is subscribing more than once (and you need multiple subscribers) then the client code is going to need to handle that. So replace

如果您的问题是客户端的单个实例订阅了多次(并且您需要多个订阅者),那么客户端代码将需要处理该问题。所以更换

not already subscribed

还没有订阅

with a bool member of the client class that gets set when you subscribe for the event the first time.

客户端类的 bool 成员在您第一次订阅事件时设置。

Edit (after accepted):Based on the comment from @Glen T (the submitter of the question) the code for the accepted solution he went with is in the client class:

编辑(接受后):根据@Glen T(问题提交者)的评论,他采用的已接受解决方案的代码在客户端类中:

if (alreadySubscribedFlag)
{
    member.Event += new MemeberClass.Delegate(handler);
}

Where alreadySubscribedFlag is a member variable in the client class that tracks first subscription to the specific event. People looking at the first code snippet here, please take note of @Rune's comment - it is not a good idea to change the behavior of subscribing to an event in a non-obvious way.

其中 alreadySubscribedFlag 是客户端类中的成员变量,用于跟踪对特定事件的首次订阅。在这里查看第一个代码片段的人,请注意@Rune 的评论 - 以不明显的方式更改订阅事件的行为不是一个好主意。

EDIT 31/7/2009:Please see comments from @Sam Saffron. As I already stated and Sam agrees the first method presented here is not a sensible way to modify the behavior of the event subscription. The consumers of the class need to know about its internal implementation to understand its behavior. Not very nice.
@Sam Saffron also comments about thread safety. I'm assuming that he is referring to the possible race condition where two subscribers (close to) simultaneously attempt to subscribe and they may both end up subscribing. A lock could be used to improve this. If you are planning to change the way event subscription works then I advise that you read about how to make the subscription add/remove properties thread safe.

编辑 31/7/2009:请参阅@Sam Saffron 的评论。正如我已经说过的,Sam 同意这里介绍的第一种方法不是修改事件订阅行为的明智方法。类的使用者需要了解其内部实现以了解其行为。不是很好。
@Sam Saffron 还评论了线程安全。我假设他指的是可能的竞争条件,即两个订阅者(接近)同时尝试订阅并且他们最终都可能订阅。可以使用锁来改善这一点。如果您打算更改事件订阅的工作方式,那么我建议您阅读有关如何使订阅添加/删除属性线程安全的信息

回答by Andrew Kennan

You would either need to store a separate flag indicating whether or not you'd subscribed or, if you have control over MemberClass, provide implementations of the add and remove methods for the event:

您要么需要存储一个单独的标志,指示您是否已订阅,或者,如果您可以控制 MemberClass,则为该事件提供 add 和 remove 方法的实现:

class MemberClass
{
        private EventHandler _event;

        public event EventHandler Event
        {
            add
            {
                if( /* handler not already added */ )
                {
                    _event+= value;
                }
            }
            remove
            {
                _event-= value;
            }
        }
}

To decide whether or not the handler has been added you'll need to compare the Delegates returned from GetInvocationList() on both _event and value.

要确定是否添加了处理程序,您需要比较从 GetInvocationList() 返回的 _event 和 value 的 Delegates。

回答by jalf

As others have shown, you can override the add/remove properties of the event. Alternatively, you may want to ditch the event and simply have the class take a delegate as an argument in its constructor (or some other method), and instead of firing the event, call the supplied delegate.

正如其他人所示,您可以覆盖事件的添加/删除属性。或者,您可能希望放弃事件,并简单地让类在其构造函数(或其他方法)中将委托作为参数,而不是触发事件,而是调用提供的委托。

Events imply that anyone can subscribe to them, whereas a delegate is onemethod you can pass to the class. Will probably be less surprising to the user of your library then, if you only use events when you actually wnat the one-to-many semantics it usually offers.

事件意味着任何人都可以订阅它们,而委托是您可以传递给类的一种方法。如果您只在实际获得它通常提供的一对多语义时才使用事件,那么对于您的库的用户来说可能就不那么令人惊讶了。

回答by alf

I'm adding this in all the duplicate questions, just for the record. This pattern worked for me:

我在所有重复的问题中添加了这个,只是为了记录。这种模式对我有用:

myClass.MyEvent -= MyHandler;
myClass.MyEvent += MyHandler;

Note that doing this every time you register your handler will ensure that your handler is registered only once.

请注意,每次注册处理程序时都执行此操作将确保您的处理程序仅注册一次。

回答by Saghar

U can use Postsharper to write one attribute just once and use it on normal Events. Reuse the code. Code sample is given below.

你可以使用 Postsharper 只编写一次属性,然后在普通事件上使用它。重用代码。代码示例如下。

[Serializable]
public class PreventEventHookedTwiceAttribute: EventInterceptionAspect
{
    private readonly object _lockObject = new object();
    readonly List<Delegate> _delegates = new List<Delegate>();

    public override void OnAddHandler(EventInterceptionArgs args)
    {
        lock(_lockObject)
        {
            if(!_delegates.Contains(args.Handler))
            {
                _delegates.Add(args.Handler);
                args.ProceedAddHandler();
            }
        }
    }

    public override void OnRemoveHandler(EventInterceptionArgs args)
    {
        lock(_lockObject)
        {
            if(_delegates.Contains(args.Handler))
            {
                _delegates.Remove(args.Handler);
                args.ProceedRemoveHandler();
            }
        }
    }
}

Just use it like this.

就这样使用它。

[PreventEventHookedTwice]
public static event Action<string> GoodEvent;

For details look at Implement Postsharp EventInterceptionAspect to prevent an event Handler hooked twice

有关详细信息,请参阅实施 Postsharp EventInterceptionAspect 以防止事件处理程序挂钩两次

回答by Tony Steel

I did this recently and I'll just drop it here so it stays:

我最近做了这个,我会把它放在这里让它保持不变:

private bool subscribed;

if(!subscribed)
{
    myClass.MyEvent += MyHandler;
    subscribed = true;
} 

private void MyHandler()
{
    // Do stuff
    myClass.MyEvent -= MyHandler;
    subscribed = false;
}