如何确保只订阅一次事件

Gle*_*n T 43 c# events event-handling subscription

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

例如,我希望能够做到以下几点:

if (*not already subscribed*)
{
    member.Event += new MemeberClass.Delegate(handler);
}
Run Code Online (Sandbox Code Playgroud)

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

alf*_*alf 62

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

myClass.MyEvent -= MyHandler;
myClass.MyEvent += MyHandler;
Run Code Online (Sandbox Code Playgroud)

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

  • 这个解决方案有一个警告.如果您在事件发生时取消订阅,您将错过该活动,因此请确保在取消订阅和订阅之间没有任何事件. (5认同)
  • 也适合我,这对我来说就像是你无法访问类的最佳解决方案(在我的例子中是Form.KeyDown) (4认同)

Ham*_*ith 38

如果您正在讨论可以访问源的类的事件,那么您可以将警卫放在事件定义中.

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

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

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

编辑请参阅评论为什么上面的代码是一个坏主意,而不是线程安全.

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

尚未订阅

与您第一次订阅该事件时设置的客户端类的bool成员.

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

if (alreadySubscribedFlag)
{
    member.Event += new MemeberClass.Delegate(handler);
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 此外,这是一个主要的反模式,任意消费者需要知道您的事件实现,以了解其行为. (2认同)

jal*_*alf 7

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

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


Sag*_*har 5

U可以使用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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它。

[PreventEventHookedTwice]
public static event Action<string> GoodEvent;
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请查看“ 实现Postsharp EventInterceptionAspect”以防止事件处理程序被钩两次