Dec 262009
 

Events and Delegates are quite tied together in .NET, but there are differences in terms of usage. Events are implemented through delegates, but they are not quite interchangeable. The event keyword is an access modifier on the delegate which restricts its usage outside the class which it belongs to.

First the similarities. See the below code, which declares a delegate and creates objects for it with and without the event modifier. Then uses both of them, without any difference whatsoever.

    class Test
    {
        public delegate void MyDelegate(string _someRandomMessage);
 
        //One is a delegate while other is an event.
        public MyDelegate _mydelObj;
        public event MyDelegate _myeventObj;
 
        public Test(){
            _mydelObj = s => Console.WriteLine("Delegate Invoked: " + s);
            _myeventObj = s => Console.WriteLine("Event Invoked: " + s);
        }
 
        public void TestDelegates(){
            _mydelObj.Invoke("I am a delegate");
            _myeventObj.Invoke("I am an event");
        }
    }

The main difference starts when you access events outside of the class they were declared in. I created one more class SomeOtherClass (very unimaginative!!) and invoked the public delegate objects from this. When its a plain delegate object, there is no restriction on resetting the invocation list, adding to it, removing from it and also invoking the delegate itself. But try doing that to the event object, and you the compiler will throw the The event ‘EventsAndDelegates.Test._myeventObj’ can only appear on the left hand side of += or -= (except when used from within the type ‘EventsAndDelegates.Test’)

    class SomeOtherClass{
        Test _testObj = new Test();
        public SomeOtherClass()
        {
            _testObj._mydelObj += s => Console.WriteLine("Calling from Outside class: "+s);
            _testObj._myeventObj += s => Console.WriteLine("Calling Event from Outside class " + s);
            //The below code wont work. An event's invocation list cannot be accessed
            //from outside. It can be added to or removed from.
            _testObj._myeventObj = s => Console.WriteLine("Trying to reset the invocation list");
        }
 
        public void InvokeBaseDelegate(){
            _testObj._mydelObj("Delegate Invoked");
            //This will throw an error. An event cannot be triggered from anyother type
            //other than the one which it belongs to
            _testObj._myeventObj("Event Triggered");
        }
    }

This key difference is due to the way events and delegates are exposed outside the class which they are declared in. There is an extra IL declaration for an event object which declares two accessors add and remove for the event. Only these are accessible outside the class.

.event EventsAndDelegates.Test/MyDelegate _myeventObj
{
  .addon instance void EventsAndDelegates.Test::add__myeventObj(class EventsAndDelegates.Test/MyDelegate)
  .removeon instance void EventsAndDelegates.Test::remove__myeventObj(class EventsAndDelegates.Test/MyDelegate)
} // end of event Test::_myeventObj

When this is accessed in SomeOtherClass, the Delegate.Combine method used to add to the delegate object’s invocation list is not available for the event and the only alternatives are the add and remove accessors. Similarly the Invoke method is not available outside the class because of which the events cant be triggered.

.method public hidebysig specialname rtspecialname
        instance void  .ctor() cil managed
{
//Removed for Brevity
  IL_0035:  ldsfld     class EventsAndDelegates.Test/MyDelegate EventsAndDelegates.SomeOtherClass::'CS$<>9__CachedAnonymousMethodDelegate2'
  IL_003a:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
                                                                                          class [mscorlib]System.Delegate)
  IL_003f:  castclass  EventsAndDelegates.Test/MyDelegate
//Removed for brevity
  IL_0067:  ldsfld     class EventsAndDelegates.Test/MyDelegate EventsAndDelegates.SomeOtherClass::'CS$<>9__CachedAnonymousMethodDelegate3'
//Notice the add_ accessor being called instead of the Delegate.Combine method
  IL_006c:  callvirt   instance void EventsAndDelegates.Test::add__myeventObj(class EventsAndDelegates.Test/MyDelegate)
  IL_0071:  ret
} // end of method SomeOtherClass::.ctor

It makes sense to restrict an event’s invocation list from an outside class. For example, if your application makes use of a public API which polls for the weather at a location and raises an event when there is going to be heavy snow. Someone in your team might have subsribed to the event to send out notifications to employees take caution while travelling. However, your need to subscribe to the event could be totally different. In that case if you use the = sign instead of the += while assigning a handler, the previous code for sending out notifications would not work. Needless to say, there would be a lot of pi**ed off employees baying for your blood the next day.

Apart from this, there is one more difference between delegates and events while declaring them in an interface. An interface cannot contain delegate objects, however they can contain events. If you try to do so, you would get an error Interfaces cannot contain fields.