112

Duplicate of: How to ensure an event is only subscribed to once and Has an event handler already been added?

I have a singleton that provides some service and my classes hook into some events on it, sometimes a class is hooking twice to the event and then gets called twice. I'm looking for a classical way to prevent this from happening. somehow I need to check if I've already hooked to this event...

Community
  • 1
  • 1
Ali Shafai
  • 5,141
  • 8
  • 34
  • 50

9 Answers9

202

How about just removing the event first with -= , if it is not found an exception is not thrown

/// -= Removes the event if it has been already added, this prevents multiple firing of the event
((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii);
((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii);
BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
PrimeTSS
  • 2,683
  • 3
  • 23
  • 26
  • 1
    Thank you. This came very handy in the same scenario (WebBrowser + HtmlElementEventHandler). Thanks for pointing this out – Odys Mar 19 '12 at 15:58
  • 2
    This should be the accepted answer as it is simple and requires no custom implementation. @LoxLox shows the same pattern as an implementation too. I didn't test, so I'm taking the comments at their word. Very nice. – Rafe Mar 23 '14 at 19:15
  • 4
    +1 I guess it's up to the programmer, but I would say this is the best (not the "neatest" to require the end-developer to do this perhaps, but it's not the event generator's fault that the subscriber can't prevent multiple subscriptions, so, make them figure out the removal, etc... besides, why prevent someone from subscribing the same handler more than once if the want to?) – Code Jockey Jun 19 '14 at 15:31
  • For engineering motivation favoring this solution (instead of the accepted one), [see this answer](http://stackoverflow.com/a/937321/3367144). – kdbanman Aug 11 '15 at 19:31
  • 1
    This approach is confirmed in [this answer](http://stackoverflow.com/a/399772/1497596) by [Jon Skeet](http://stackoverflow.com/users/22656/jon-skeet) to a similar question. (Although Jon also shows how to explicitly implement the event handler itself to prevent duplicates, rather than leaving the prevention of possible duplicates up to the subscriber.) – DavidRR Aug 12 '15 at 14:47
  • This is not working for me in an Outlook 2010 VSTO add-in. I'm trying to just add an event handler (MailItem.Reply) once, and even put a lock around this block (which is the only place I interact with this event handler), but the handler function gets called multiple times anyhow! `mailE.Reply -= MailItemResponseHandler; mailE.Reply += MailItemResponseHandler;` – CBHacking Jul 21 '16 at 02:44
  • 2
    Also for this be aware of thread safety as stated : https://stackoverflow.com/questions/367523/how-to-ensure-an-event-is-only-subscribed-to-once#comment68420379_7065833 – Кое Кто Mar 17 '20 at 17:12
162

Explicitly implement the event and check the invocation list. You'll also need to check for null:

using System.Linq; // Required for the .Contains call below:

...

private EventHandler foo;
public event EventHandler Foo
{
    add
    {
        if (foo == null || !foo.GetInvocationList().Contains(value))
        {
            foo += value;
        }
    }
    remove
    {
        foo -= value;
    }
}

Using the code above, if a caller subscribes to the event multiple times, it will simply be ignored.

Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
  • 14
    You need to use the System.Linq using. –  Jun 18 '09 at 17:55
  • Just to clarify Hermann's comment; you have to include the namespace 'System.Linq' by adding 'using System.Linq' to your class or current namespace. – Kjetil Klaussen Aug 13 '10 at 11:34
  • Fascinating. LINQ is still new enough to me that I have to look it up and be reminded it means Language Integrated Query... and then wonder what that has to do with EventHandlers and their InvocationList? – fortboise Nov 11 '11 at 20:54
  • 6
    Contains is an extension method that lets you *query* a list. It comes with LINQ because it's a query aid. It's related to EventHandlers and invocation lists because those are things are lists that can be queried. – Judah Gabriel Himango Nov 11 '11 at 21:30
  • I prefer the -= approach as described in another post. – ArildF Jun 29 '13 at 21:53
  • 3
    loved this! I removed the second condition in the if statement so i can attach only a single delegate to the Foo event, witch is what i needed! Great solution ;) – HypeZ Apr 28 '14 at 07:57
  • 3
    While this answer does solve the problem, [engineers should be careful when prescribing event handler behavior at the event source rather than at the event handler itself](http://stackoverflow.com/a/937321/3367144). @PrimeTSS answer provides an alternative that leaves event handling behavior (like duplicate subscription) to the handler itself. – kdbanman Aug 11 '15 at 19:36
  • 4
    Calls to `Invoke()` will have to be switched from the public event to the private delegate, e.g. `Foo?.Invoke()` would now become `foo?.Invoke()`. Otherwise you get an error. – toddmo Oct 19 '17 at 16:19
  • Can we write an extension method to do this outside of the event source? – Spongebob Comrade Sep 09 '19 at 02:01
  • @SpongebobComrade I'm not sure - give it a shot and let me know how it goes. Obviously, we don't have extension events, so it'd need to be a method. But maybe it works anyway. – Judah Gabriel Himango Sep 11 '19 at 14:23
  • @JudahGabrielHimango I gave it a try and it is not possible. If it was possible it would have been very great. – Spongebob Comrade Sep 12 '19 at 01:50
  • Thanks for letting us know. – Judah Gabriel Himango Sep 12 '19 at 21:04
  • This didn't work for me, and I think it's because of multithreading. The "value" wasn't the same object as previous versions of the event, so it was never equivalent and was added again and again etc. I had to verify that the value.Method wasn't already added before adding the value event. if (foo == null || !foo.GetInvocationList().Select(il => il.Method).Contains(value.Method)) – computercarguy Jul 30 '20 at 18:48
  • I ended up adding a new Answer to the Q this is a duplicate of. The `remove` isn't straight forward either. https://stackoverflow.com/a/63179902/1836461 – computercarguy Jul 30 '20 at 19:19
49

I've tested each solution and the best one (considering performance) is:

private EventHandler _foo;
public event EventHandler Foo {

    add {
        _foo -= value;
        _foo += value;
    }
    remove {
        _foo -= value;
    }
}

No Linq using required. No need to check for null before cancelling a subscription (see MS EventHandler for details). No need to remember to do the unsubscription everywhere.

LoxLox
  • 977
  • 9
  • 16
21

You really should handle this at the sink level and not the source level. That is, don't prescribe event handler logic at the event source - leave that to the handlers (the sinks) themselves.

As the developer of a service, who are you to say that sinks can only register once? What if they want to register twice for some reason? And if you are trying to correct bugs in the sinks by modifying the source, it's again a good reason for correcting these issues at the sink-level.

I'm sure you have your reasons; an event source for which duplicate sinks are illegal is not unfathomable. But perhaps you should consider an alternate architecture that leaves the semantics of an event intact.

kdbanman
  • 10,161
  • 10
  • 46
  • 78
JP Alioto
  • 44,864
  • 6
  • 88
  • 112
  • This is an excellent engineering justification for [this solution](http://stackoverflow.com/a/1104269/3367144), which solves the problem on the event sink (subscriber/consumer/observer/handler) side instead of the source side. – kdbanman Aug 11 '15 at 19:28
14

You need to implement the add and remove accessors on the event, and then check the target list of the delegate, or store the targets in a list.

In the add method, you can use the Delegate.GetInvocationList method to obtain a list of the targets already added to the delegate.

Since delegates are defined to compare equal if they're linked to the same method on the same target object, you could probably run through that list and compare, and if you find none that compares equal, you add the new one.

Here's sample code, compile as console application:

using System;
using System.Linq;

namespace DemoApp
{
    public class TestClass
    {
        private EventHandler _Test;

        public event EventHandler Test
        {
            add
            {
                if (_Test == null || !_Test.GetInvocationList().Contains(value))
                    _Test += value;
            }

            remove
            {
                _Test -= value;
            }
        }

        public void OnTest()
        {
            if (_Test != null)
                _Test(this, EventArgs.Empty);
        }
    }

    class Program
    {
        static void Main()
        {
            TestClass tc = new TestClass();
            tc.Test += tc_Test;
            tc.Test += tc_Test;
            tc.OnTest();
            Console.In.ReadLine();
        }

        static void tc_Test(object sender, EventArgs e)
        {
            Console.Out.WriteLine("tc_Test called");
        }
    }
}

Output:

tc_Test called

(ie. only once)

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
5

Microsoft's Reactive Extensions (Rx) framework can also be used to do "subscribe only once".

Given a mouse event foo.Clicked, here's how to subscribe and receive only a single invocation:

Observable.FromEvent<MouseEventArgs>(foo, nameof(foo.Clicked))
    .Take(1)
    .Subscribe(MyHandler);

...

private void MyHandler(IEvent<MouseEventArgs> eventInfo)
{
   // This will be called just once!
   var sender = eventInfo.Sender;
   var args = eventInfo.EventArgs;
}

In addition to providing "subscribe once" functionality, the RX approach offers the ability to compose events together or filter events. It's quite nifty.

Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
2

Create an Action instead of an event. Your class may look like:

public class MyClass
{
                // sender   arguments       <-----     Use this action instead of an event
     public Action<object, EventArgs> OnSomeEventOccured;

     public void SomeMethod()
     {
          if(OnSomeEventOccured!=null)
              OnSomeEventOccured(this, null);
     }

}
Tono Nam
  • 34,064
  • 78
  • 298
  • 470
0

In silverlight you need to say e.Handled = true; in the event code.

void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    e.Handled = true; //this fixes the double event fire problem.
    string name = (e.OriginalSource as Image).Tag.ToString();
    DoSomething(name);
}

Please tick me if this helps.

Omzig
  • 861
  • 1
  • 12
  • 20
0

have your singleton object check it's list of who it notifies and only call once if duplicated. Alternatively if possible reject event attachment request.

Toby Allen
  • 10,997
  • 11
  • 73
  • 124