In a code review, I stumbled over this (simplified) code fragment to unregister an event handler:
Fire -= new MyDelegate(OnFire);
I thought that this does not unregister the event handler because it creates a new delegate which had never been registered before. But searching MSDN I found several code samples which use this idiom.
So I started an experiment:
internal class Program { public delegate void MyDelegate(string msg); public static event MyDelegate Fire; private static void Main(string[] args) { Fire += new MyDelegate(OnFire); Fire += new MyDelegate(OnFire); Fire("Hello 1"); Fire -= new MyDelegate(OnFire); Fire("Hello 2"); Fire -= new MyDelegate(OnFire); Fire("Hello 3"); } private static void OnFire(string msg) { Console.WriteLine("OnFire: {0}", msg); } }
To my surprise, the following happened:
Fire("Hello 1");
produced two messages, as expected.Fire("Hello 2");
produced one message!new
delegates works!Fire("Hello 3");
threw a NullReferenceException
.Fire
is null
after unregistering the event.I know that for event handlers and delegate, the compiler generates a lot of code behind the scene. But I still don't understand why my reasoning is wrong.
What am I missing?
Additional question: from the fact that Fire
is null
when there are no events registered, I conclude that everywhere an event is fired, a check against null
is required.
The removeEventListener() is an inbuilt function in JavaScript which removes an event handler from an element for a attached event. for example, if a button is disabled after one click you can use removeEventListener() to remove a click event listener.
Add the event listener in the useEffect hook. Return a function from the useEffect hook. Use the removeEventListener method to remove the event listener when the component unmounts.
The C# compiler's default implementation of adding an event handler calls Delegate.Combine
, while removing an event handler calls Delegate.Remove
:
Fire = (MyDelegate) Delegate.Remove(Fire, new MyDelegate(Program.OnFire));
The Framework's implementation of Delegate.Remove
doesn't look at the MyDelegate
object itself, but at the method the delegate refers to (Program.OnFire
). Thus, it's perfectly safe to create a new MyDelegate
object when unsubscribing an existing event handler. Because of this, the C# compiler allows you to use a shorthand syntax (that generates exactly the same code behind the scenes) when adding/removing event handlers: you can omit the new MyDelegate
part:
Fire += OnFire; Fire -= OnFire;
When the last delegate is removed from the event handler, Delegate.Remove
returns null. As you have found out, it's essential to check the event against null before raising it:
MyDelegate handler = Fire; if (handler != null) handler("Hello 3");
It's assigned to a temporary local variable to defend against a possible race condition with unsubscribing event handlers on other threads. (See my blog post for details on the thread safety of assigning the event handler to a local variable.) Another way to defend against this problem is to create an empty delegate that is always subscribed; while this uses a little more memory, the event handler can never be null (and the code can be simpler):
public static event MyDelegate Fire = delegate { };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With