subscribe / unsubscribe to an event

In one of the internal aliases there was a discussion going on some time back regarding which is the better way to unsubscribe from an event and why does the first one even work

 

  1. myClass1.MyEvent += new EventHandler(Function);
    Do all stuff
    myClass1.MyEvent -= new EventHandler(Function);

 

  1. EventHandler eventHandler = new EventHandler(Function);
    myClass2.MyEvent += eventHandler;
    myClass2.OnEvent();
    myClass2.MyEvent -= eventHandler;

 

In the first approach different objects are used to add and remove the event subscription. This can work only if the removal mechanism does not work by just comparing references. Because if it did then the removal mechanism would not find the object in the methods invocation list of the underlying delegate and would simply not remove the method.

 

I thought I’d do a little more snooping into this.

 

A class definition like
 public delegate void EventHandler(Object source, EventArgs eventArgs);

    class MyClass

    {

        public event EventHandler MyEvent;

}

Is converted to code equivalent to

 

    class MyClass

    {

        private EventHandler __MyEvent;

        public event EventHandler MyEvent

        {

            add

            {

                lock (this) { __MyEvent += value; }

            }

            remove

            {

                lock (this) { __MyEvent -= value; }

            }

        }

}

So the compiler generates the add/remove accessors.

 

If you see the code of the accessors in ILDasm then you can see for the add_MyEvent method compiler emits code to call the System.Delegate.Combine method to combine the existing delegate and new delegate and for remove_MyEvent it uses System.Delegate.Remove method.

 

So

myClass1.MyEvent += new EventHandler(Function); è System.Delegate.Combine (myClass1.__MyEvent, newdelegate)

and

myClass1.MyEvent -= new EventHandler(Function); è System.Delegate.Remove (myClass1, newdelegate)

 

So all of the trick lies in the Combine and Remove methods of System.Delegate.

 

System.Delegate.Remove goes through the invocation list of the first delegate looking for invocations from the second delegate. If there is a match it removes it. System.Delegate overrides the == (Equals) operator so that the match does not happen based on object reference but based on the method the delegate encapsulates. This operator compares the instance object (null in case of static method callback) and the function pointed to and then removes the callback from the invocation list.

 

So both works but which one should we use? If the events are subscribed/unsubscribed once at the beginning/end like in a typical WinForm application then it hardly matters. However if this is done multiple times then the second approach is preferable as it does less of costly heap allocations and will work faster.

 

The complete code listing of the sample I used is as follows

 

using System;

using System.Collections.Generic;

using System.Text;

namespace EventAddRemove

{

    public delegate void EventHandler(Object source, EventArgs eventArgs);

    class MyClass

    {

        public event EventHandler MyEvent;

public void OnEvent()

        {

            if (MyEvent != null)

                MyEvent(this, EventArgs.Empty);

            else

                Console.WriteLine("No Event to fire");

        }

    }

   

    class Program

    {

        static void Function(object source, EventArgs eventArgs)

        {

            Console.WriteLine("Event got fired");

        }

        static void Main(string[] args)

        {

            Console.WriteLine("myClass1");

            MyClass myClass1 = new MyClass();

            myClass1.MyEvent += new EventHandler(Function);

            myClass1.OnEvent();

            myClass1.MyEvent -= new EventHandler(Function);

            myClass1.OnEvent();

            Console.WriteLine("myClass2");

            EventHandler eventHandler = new EventHandler(Function);

            MyClass myClass2 = new MyClass();

            myClass2.MyEvent += eventHandler;

            myClass2.OnEvent();

            myClass2.MyEvent -= eventHandler;

            myClass2.OnEvent();

        }

    }

}

Output is

myClass1

Event got fired

No Event to fire

myClass2

Event got fired

No Event to fire

Lines in bold indicate that the correct methods was indeed removed from the invocation list in both approaches.