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

发布于 2024-07-09 14:37:45 字数 192 浏览 9 评论 0原文

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

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

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

雪化雨蝶 2024-07-16 14:37:45

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

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

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

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.

懒猫 2024-07-16 14:37:45

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

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

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

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

编辑请参阅有关为什么上述代码是一个坏主意并且不是线程安全的评论。

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

尚未订阅

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

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

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

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

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

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.

EDIT please 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.

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:

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.

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.

无力看清 2024-07-16 14:37:45

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

事件意味着任何人都可以订阅它们,而委托是您可以传递给类的一个方法。 如果您仅在真正需要它通常提供的一对多语义时才使用事件,那么您的库的用户可能不会感到惊讶。

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 one method 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 want the one-to-many semantics it usually offers.

山田美奈子 2024-07-16 14:37:45

您可以使用 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 以阻止事件发生处理程序挂接两次

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

[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

呆° 2024-07-16 14:37:45

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

class MemberClass
{
        private EventHandler _event;

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

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

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:

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.

不必了 2024-07-16 14:37:45

我知道这是一个老问题,但当前的答案对我不起作用。

查看 C# 模式以防止事件处理程序挂钩两次< /a> (标记为这个问题的重复项),给出了更接近的答案,但仍然不起作用,可能是因为多线程导致新的事件对象不同,或者可能是因为我正在使用自定义事件类。 我最终得到了与上述问题的已接受答案类似的解决方案。

private EventHandler<bar> foo;
public event EventHandler<bar> Foo
{
    add
    {
        if (foo == null || 
            !foo.GetInvocationList().Select(il => il.Method).Contains(value.Method))
        {
            foo += value;
        }
    }

    remove
    {
        if (foo != null)
        {
            EventHandler<bar> eventMethod = (EventHandler<bar>)foo .GetInvocationList().FirstOrDefault(il => il.Method == value.Method);

            if (eventMethod != null)
            {
                foo -= eventMethod;
            }
        }
    }
}

这样,您还必须使用 foo.Invoke(...) 而不是 Foo.Invoke(...) 来触发事件。 如果您尚未使用 System.Linq,则还需要包含它。

这个解决方案并不完全漂亮,但它有效。

I know this is an old Question, but the current Answers didn't work for me.

Looking at C# pattern to prevent an event handler hooked twice (labelled as a duplicate of this question), gives Answers that are closer, but still didn't work, possibly because of multi-threading causing the new event object to be different or maybe because I was using a custom event class. I ended up with a similar solution to the accepted Answer to the above Question.

private EventHandler<bar> foo;
public event EventHandler<bar> Foo
{
    add
    {
        if (foo == null || 
            !foo.GetInvocationList().Select(il => il.Method).Contains(value.Method))
        {
            foo += value;
        }
    }

    remove
    {
        if (foo != null)
        {
            EventHandler<bar> eventMethod = (EventHandler<bar>)foo .GetInvocationList().FirstOrDefault(il => il.Method == value.Method);

            if (eventMethod != null)
            {
                foo -= eventMethod;
            }
        }
    }
}

With this, you'll also have to fire your event with foo.Invoke(...) instead of Foo.Invoke(...). You'll also need to include System.Linq, if you aren't already using it.

This solution isn't exactly pretty, but it works.

知足的幸福 2024-07-16 14:37:45

我最近做了这个,我会把它放在这里,这样它就保留了:

private bool subscribed;

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

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

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;
}
白鸥掠海 2024-07-16 14:37:45

引发时仅调用 GetInitationList 中的不同元素:

using System.Linq;
....
public event HandlerType SomeEvent;
....
//Raising code
foreach (HandlerType d in (SomeEvent?.GetInvocationList().Distinct() ?? Enumerable.Empty<Delegate>()).ToArray())
     d.Invoke(sender, arg);

示例单元测试:

class CA 
{
    public CA()
    { }
    public void Inc()
        => count++;
    public int count;
}
[TestMethod]
public void TestDistinctDelegates()
{
    var a = new CA();
    Action d0 = () => a.Inc();
    var d = d0;
    d += () => a.Inc();
    d += d0;
    d.Invoke();
    Assert.AreEqual(3, a.count);
    var l = d.GetInvocationList();
    Assert.AreEqual(3, l.Length);
    var distinct = l.Distinct().ToArray();
    Assert.AreEqual(2, distinct.Length);
    foreach (Action di in distinct)
        di.Invoke();
    Assert.AreEqual(3 + distinct.Length, a.count);
}
[TestMethod]
public void TestDistinctDelegates2()
{
    var a = new CA();
    Action d = a.Inc;
    d += a.Inc;
    d.Invoke();
    Assert.AreEqual(2, a.count);
    var distinct = d.GetInvocationList().Distinct().ToArray();
    Assert.AreEqual(1, distinct.Length);
    foreach (Action di in distinct)
        di.Invoke();
    Assert.AreEqual(3, a.count);
}

Invoke only distinct elements from GetInvocationList while raising:

using System.Linq;
....
public event HandlerType SomeEvent;
....
//Raising code
foreach (HandlerType d in (SomeEvent?.GetInvocationList().Distinct() ?? Enumerable.Empty<Delegate>()).ToArray())
     d.Invoke(sender, arg);

Example unit test:

class CA 
{
    public CA()
    { }
    public void Inc()
        => count++;
    public int count;
}
[TestMethod]
public void TestDistinctDelegates()
{
    var a = new CA();
    Action d0 = () => a.Inc();
    var d = d0;
    d += () => a.Inc();
    d += d0;
    d.Invoke();
    Assert.AreEqual(3, a.count);
    var l = d.GetInvocationList();
    Assert.AreEqual(3, l.Length);
    var distinct = l.Distinct().ToArray();
    Assert.AreEqual(2, distinct.Length);
    foreach (Action di in distinct)
        di.Invoke();
    Assert.AreEqual(3 + distinct.Length, a.count);
}
[TestMethod]
public void TestDistinctDelegates2()
{
    var a = new CA();
    Action d = a.Inc;
    d += a.Inc;
    d.Invoke();
    Assert.AreEqual(2, a.count);
    var distinct = d.GetInvocationList().Distinct().ToArray();
    Assert.AreEqual(1, distinct.Length);
    foreach (Action di in distinct)
        di.Invoke();
    Assert.AreEqual(3, a.count);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文