219

Is there a way to tell if an event handler has been added to an object? I'm serializing a list of objects into/out of session state so we can use SQL based session state... When an object in the list has a property changed it needs to be flagged, which the event handler took care of properly before. However now when the objects are deserialized it isn't getting the event handler.

In an fit of mild annoyance, I just added the event handler to the Get property that accesses the object. It's getting called now which is great, except that it's getting called like 5 times so I think the handler just keeps getting added every time the object is accessed.

It's really safe enough to just ignore, but I'd rather make it that much cleaner by checking to see if the handler has already been added so I only do so once.

Is that possible?

EDIT: I don't necessarily have full control of what event handlers are added, so just checking for null isn't good enough.

bdukes
  • 152,002
  • 23
  • 148
  • 175
CodeRedick
  • 7,346
  • 7
  • 46
  • 72

8 Answers8

260

I recently came to a similar situation where I needed to register a handler for an event only once. I found that you can safely unregister first, and then register again, even if the handler is not registered at all:

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. Sounds like a pretty good practice to me :)

alf
  • 18,372
  • 10
  • 61
  • 92
  • 10
    Seems risky; if an event is fired after removing the handler and before adding it back, it will be missed. – Jimmy Jun 04 '12 at 16:13
  • 34
    Sure. You mean it's not thread safe. But that can only be a problem when running multiple threads or the like, which is not usual. In most cases this should be good enough for the sake of simplicity. – alf Jun 05 '12 at 00:54
  • I think that this is the best way to do it just make a practice to whenever you add it subtract it anyway then you can be safer. otherwise you will be striking your head to wall by finding out that same method is being called twice – Mubashar Feb 09 '13 at 06:05
  • 11
    This bothers me. Just because you aren't currently explicitly creating threads in your code, that doesn't mean there aren't multiple threads, or that they won't be added later. As soon as you (or someone else in the team, possibly months later) add a worker thread or respond to both the UI and to network connection, this opens the door to highly intermittent dropped events. – Technophile Aug 14 '15 at 21:46
  • 1
    I believe the time window between removing and adding again is so small, that is very unlikely to exist missed events, but yeah, it's still possible. – Alisson Reinaldo Silva Aug 17 '17 at 14:25
  • 2
    If you are using this for something like updating a UI etc then its such a trivial task the risk is ok. If this was for network packet handling etc then I would not use it. – rollsch Sep 13 '17 at 01:49
  • @alf: In a web-application, it's not only usual, it's also possible with a single-user. – Stefan Steiger Sep 07 '18 at 13:58
  • You can always synchronize. Suspend firing the event until the remove/add is done. – ed22 May 10 '19 at 13:21
  • This does not work with some event handlers such as: `MediaPlayer.Changed -= Player_Changed;` It throws an exception `Handler has not been registered with this event`. – Nicke Manarin Nov 12 '19 at 14:47
139

From outside the defining class, as @Telos mentions, you can only use EventHandler on the left-hand side of a += or a -=. So, if you have the ability to modify the defining class, you could provide a method to perform the check by checking if the event handler is null - if so, then no event handler has been added. If not, then maybe and you can loop through the values in Delegate.GetInvocationList. If one is equal to the delegate that you want to add as event handler, then you know it's there.

public bool IsEventHandlerRegistered(Delegate prospectiveHandler)
{   
    if ( this.EventHandler != null )
    {
        foreach ( Delegate existingHandler in this.EventHandler.GetInvocationList() )
        {
            if ( existingHandler == prospectiveHandler )
            {
                return true;
            }
        }
    }
    return false;
}

And this could easily be modified to become "add the handler if it's not there". If you don't have access to the innards of the class that's exposing the event, you may need to explore -= and +=, as suggested by @Lou Franco.

However, you may be better off reexamining the way you're commissioning and decommissioning these objects, to see if you can't find a way to track this information yourself.

Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • 9
    This doesn't compile, EventHandler can only be on the left hand side of += or -=. – CodeRedick Sep 26 '08 at 14:00
  • 2
    Removed down vote upon further explanation. SQL state is pretty much destroying the entire idea here... :( – CodeRedick Sep 26 '08 at 18:25
  • 1
    Thanks Blair and SO search, just what I was looking for (annoying you can't do it outside the class though) – George Mauer Dec 19 '08 at 14:39
  • Actually, this is a GREAT answer that solved a very tricky bug in my code. Thanks a ton! – andreapier Aug 14 '13 at 14:37
  • 4
    Problem occurs most of the time while comparing the delegates for equality. So use `Delegate.Equals(objA, objB)` if you want to check for exactly same delegate existance. Otherwise compare the properties individually like `if(objA.Method.Name == objB.Method.Name && objA.Target.GetType().FullName == objB.Target.GetType().FullName)`. – Sanjay Aug 06 '14 at 13:21
  • 3
    This code isn't working in a WinForm. Is it strictly for ASP.NET? –  May 22 '15 at 19:57
  • @Sanjay You're right and saved me a lot of searches, as Blair's code throws a warning of a possible comparison of references. – Sierramike Dec 29 '16 at 14:07
  • Instead of this ugly loop, "Contains" method would look better. ((IList)EventHandler.GetInvocationList()).Contains(prospectiveHandler) – Xtro Oct 27 '17 at 14:53
  • public static bool IsRegistered(this EventHandler handler, Delegate prospectiveHandler) => handler != null && handler.GetInvocationList().Any(existingHandler => existingHandler == prospectiveHandler); – Latency Jul 10 '18 at 19:55
18

If this is the only handler, you can check to see if the event is null, if it isn't, the handler has been added.

I think you can safely call -= on the event with your handler even if it's not added (if not, you could catch it) -- to make sure it isn't in there before adding.

Lou Franco
  • 87,846
  • 14
  • 132
  • 192
8

This example shows how to use the method GetInvocationList() to retrieve delegates to all the handlers that have been added. If you are looking to see if a specific handler (function) has been added then you can use array.

public class MyClass
{
  event Action MyEvent;
}

...

MyClass myClass = new MyClass();
myClass.MyEvent += SomeFunction;

...

Action[] handlers = myClass.MyEvent.GetInvocationList(); //this will be an array of 1 in this example

Console.WriteLine(handlers[0].Method.Name);//prints the name of the method

You can examine various properties on the Method property of the delegate to see if a specific function has been added.

If you are looking to see if there is just one attached, you can just test for null.

Jason Jackson
  • 17,016
  • 8
  • 49
  • 74
  • 1
    GetInvocationList() is not a member of my class. In fact, I can't seem to find this method on any object or handler I have access to... – CodeRedick Sep 26 '08 at 13:59
  • I had tried that too, apparently you can only access the event like that from inside the class. I'm doing this generically, and as others have mentioned event handlers are probably getting lost anyway. Thanks for the clarification! – CodeRedick Sep 26 '08 at 18:27
6

The only way that worked for me is creating a Boolean variable that I set to true when I add the event. Then I ask: If the variable is false, I add the event.

bool alreadyAdded = false;

This variable can be global.

if(!alreadyAdded)
{
    myClass.MyEvent += MyHandler;
    alreadyAdded = true;
}
Xtian11
  • 2,130
  • 1
  • 21
  • 13
5

If I understand your problem correctly you may have bigger issues. You said that other objects may subscribe to these events. When the object is serialized and deserialized the other objects (the ones that you don't have control of) will lose their event handlers.

If you're not worried about that then keeping a reference to your event handler should be good enough. If you are worried about the side-effects of other objects losing their event handlers, then you may want to rethink your caching strategy.

George Stocker
  • 57,289
  • 29
  • 176
  • 237
CodeChef
  • 906
  • 1
  • 7
  • 21
  • 1
    D'oh! Hadn't even thought of that... though it should have been obvious considering my original problem was my own handler getting lost. – CodeRedick Sep 26 '08 at 18:23
2
EventHandler.GetInvocationList().Length > 0
Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162
benPearce
  • 37,735
  • 14
  • 62
  • 96
2

i agree with alf's answer,but little modification to it is,, to use,

           try
            {
                control_name.Click -= event_Click;
                main_browser.Document.Click += Document_Click;
            }
            catch(Exception exce)
            {
                main_browser.Document.Click += Document_Click;
            }