如何确保一次活动只订阅一次

问题描述:

我想确保我只在一个实例的某个特定类中订阅一次。如何确保一次活动只订阅一次

比如我想能够做到以下几点:

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

我如何去实现这样的后卫?

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

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

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

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

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

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

尚未订阅

与当您订阅该事件的第一次时设置客户端类的布尔成员。

编辑(后接受):基于从@Glen T(问题的提交者)为接受的解决方案,他跟去了代码的注释是在客户端类:

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

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

编辑31/7/2009:请参阅@Sam Saffron的评论。正如我已经说过的,Sam同意这里介绍的第一种方法不是修改事件订阅行为的明智方法。该班级的消费者需要了解其内部实施情况以了解其行为。不大好。
@Sam Saffron也评论了线程安全。我假设他指的是两个用户(接近)同时尝试订阅的可能的竞争状态,他们可能最终订阅。锁可以用来改善这一点。如果你打算改变事件订阅的方式,那么我建议你read about how to make the subscription add/remove properties thread safe

+0

我想我会继续使用布尔成员变量的方法。 但是我有点惊讶,没有其他方法来检查客户是否已经订阅。我会认为给定的客户只想订阅一次是比较常见的? – 2008-12-15 07:26:25

+1

根据您的设置,如果事件已经有用户,您可能想要抛出异常。如果您在运行时添加订户,则会通知他们错误,而不是无所事事。在没有通知用户的情况下更改默认行为并不是最佳做法。 – 2008-12-15 07:58:32

你要么需要存储一个独立的标志,指示你是否愿意认购或者,如果你有过成员类的控制,提供了附加的实现,并删除该事件的方法:

class MemberClass 
{ 
     private EventHandler _event; 

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

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

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

事件意味着任何人都可以订阅他们,而代表是一个您可以传递给该类的方法。那么,如果你只在事实上包含了它通常提供的一对多语义时才使用事件,那么对于你的图书馆用户来说,可能会不那么令人惊讶。

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

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

注意,这样做,每次您注册的处理程序将确保您的处理程序注册一次。

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

就这样使用它。

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

详情看Implement Postsharp EventInterceptionAspect to prevent an event Handler hooked twice

在我看来,像一个简单的方法来做到这一点是退订处理程序(如果没有订阅,这将失败默默)和然后订阅。

member.Event -= eventHandler; 
member.Event += eventHandler; 

确实发生在开发商,这是错误的方式做到这一点负担,但是对于快速和肮脏,这是快速和肮脏。