如何确保一个事件只订阅一次
我想确保我只在一个特定的类别上订阅一个事件。
例如,我希望能够做到以下几点:
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; } }
这将确保只有一个订阅者可以在提供事件的类的这个实例上订阅事件。
编辑请看看为什么上面的代码是一个坏主意,而不是线程安全的意见。
如果您的问题是客户端的单个实例不止一次订阅(并且您需要多个订户),那么客户端代码将需要处理该客户端代码。 所以replace
尚未订阅
与第一次订阅活动时设置的客户端类的bool成员关联。
编辑(在接受之后):基于来自@Glen T(提问者)的评论,他接受的解决scheme的代码在客户端类中:
if (alreadySubscribedFlag) { member.Event += new MemeberClass.Delegate(handler); }
其中alreadySubscribedFlag是跟踪对特定事件的第一个订阅的客户端类中的成员variables。 在这里查看第一个代码片段的人请注意@ Rune的评论 – 以非明显的方式改变订阅事件的行为并不是一个好主意。
编辑31/7/2009:请参阅@Sam Saffron的评论。 正如我已经说过,山姆同意,这里提出的第一种方法是不是一个明智的方式来修改事件订阅的行为。 class级的消费者需要了解其内部的实施情况,以了解其行为。 不是很好。
@Sam藏红花也评论线程安全。 我假定他指的是两个用户(接近)同时尝试订阅的可能的竞争条件,他们可能最终订阅。 锁可以用来改善这一点。 如果您打算更改事件订阅的工作方式,那么我build议您阅读如何使订阅添加/删除属性线程安全 。
我在所有重复的问题中join这个,只是为了logging。 这种模式对我有用:
myClass.MyEvent -= MyHandler; myClass.MyEvent += MyHandler;
请注意,每次注册处理程序时都要这样做,以确保您的处理程序只注册一次。
正如其他人所示,您可以覆盖事件的添加/删除属性。 或者,您可能想要抛弃事件,只需让该类在其构造函数(或其他方法)中将委托作为参数,而不是触发事件,则调用提供的委托。
事件意味着任何人都可以订阅他们,而代表是你可以传递给class级的一种方法。 那么,如果你只是在事实上包含了它通常提供的一对多语义的时候才使用事件,那么对你的图书馆用户来说可能就不那么令人惊讶了。
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;
有关详细信息,请参阅实现Postsharp EventInterceptionAspect以防止事件处理程序挂钩两次
你要么需要存储一个单独的标志,指出你是否已经订阅,或者,如果你有控制MemberClass,提供事件的添加和删除方法的实现:
class MemberClass { private EventHandler _event; public event EventHandler Event { add { if( /* handler not already added */ ) { _event+= value; } } remove { _event-= value; } } }
要决定是否已经添加了处理程序,您需要比较从_event和value的GetInvocationList()返回的委托。
在我看来,一个简单的方法就是取消订阅你的处理程序(如果没有订阅,将会自动失败), 然后订阅。
member.Event -= eventHandler; member.Event += eventHandler;
它确实给开发者造成了负担,这是错误的做法,但是为了快速和肮脏,这是快速和肮脏的。