Understanding C# Delegates and Events

This post is about an extremely seasoned topic from C#, which many new programmers face difficulty understanding – Delegates and Events.

I have noticed, even a C# programmers with over 5 years of experience become a bit nervous when they are asked to explain delegates in the interviews and sometimes even go on the back-foot saying they have used it very little. On the other hand, the idea of delegates and functional programming is so prevalent in C# that any programmer have hardly gotten away from using it.

So, in this post I am going to go through the basics of C# delegates and events such that they no longer feel confusing and complex.

Imagine you require a variable to reference a method. A variable that encapsulates executable lines of code and can be invoked like a method. A delegate is nothing but a way to provide this functionality in C#.

Delegate is a user defined type (just like a class or a struct) that can hold a reference to certain method. Simple enough?

The only condition is that, the delegate declaration should match the signature of method it can hold reference to. i.e. the input parameters it take and the type of value it can return.

So let’s take a look at declaring a delegate. Suppose we want to create a delegate that can hold a reference to method taking two string inputs and returns a void.

namespace DelegateDemo
{
     public delegate void NameChangedDelegate(string oldName, string newName);
}

The code above declares delegate type NameChangedDelegate which can hold any number of methods that take two string arguments and returns void. This is analogous to declaring a class. Note that here, the name of arguments in the delegate declaration do not matter and need not match with actual methods that are held by this delegate.

Now that we have declared a delegate let’s now create an instance of the same (much like how we instantiate a class) wherever we want to use this delegate.

namespace DelegateDemo
{
     public class DelegateUser
     {
        public NameChangedDelegate NameChanged;
        public DelegateUser()
        {
             NameChanged = new NameChangedDelegate(OnNameChanged);
        }

        public void OnNameChanged(string oldNameValue, string newNameValue)
        {
             Console.WriteLine("The name has been changed from 
                  {0} to {1}", oldNameValue, newNameValue);
        }
     } 
}

Note that the method OnNameChanged is added to the delegate at the time of initialization which can be invoked anonymously using the delegate instance. The signature of the delegate has to match with the signature specified in delegate type.

This is all the wiring we need. Now, whenever we want to call the method OnNameChanged we simply need to invoke the delegate as below

namespace DelegateDemo
{
   public class Program
   {
       public static void main()
       {
           string oldName = "Hastinapur";
           string newName = "Delhi";
           
           DelegateUser du = new DelegateUser();
           NameChanged(oldname, newName);
        }
}

// Outputs to console
The name has been changed from Hastinapur to Delhi

The real use case for delegate becomes clear when we dig into what is called a multicast delegate

A multicast delegate is the one, to which more than one methods can be added. The condition is that all methods should abide by the method signature as specified by delegate. Using delegate instance all these methods can be invoked anonymously.

So in order to add more than one method to a delegate we can use += operator.

NameChanged += OnCityNameChanged;
NameChanged += OnStateNameChanged;
NameChanged += OnCountryNameChanged;

In the code above, the NameChanged delegate holds, actually holds a reference to four methods three methods added using += operator above and the one added when instantiating a delegate type.

Similarly, in order remove reference of a method from a delegate we can use -= operator.

NameChanged -= OnCountryNameChanged

Now, here is a problem with multicast delegate, suppose somewhere in the class DelegateUser, someone mistakenly re-instantiates a delegate as below:

NameChanged = new NameChangedDelegate(OnTownNameChanged);

This will basically, wipe out all previous method references that delegate was holding on to! So, the calling the delegate instance to invoke underlying methods, will only invoke one method OnTownNameChanged

NameChanged("SomeOldName", "SomeNewName");

//Expected output:
The name has been changed from SomeOldName to SomeNewName
The city has been changed from SomeOldName to SomeNewName
The state has been changed from SomeOldName to SomeNewName
The town has been changed from SomeOldName to SomeNewName

// Actual output
The town has been changed from SomeOldName to SomeNewName

In order to avoid this issue, C# introduces a keyword event. An event is a type of multicast delegate that can only be subscribed to or unsubscribed from, but cannot be instantiated on its own.

Here is how we declare an event

namespace EventDemo
{
     public delegate void NameChangedDelegate(string oldName, string newName);
     public event NameChangedDelegate OnNameChanged
}

By decorating the instance of NameChangeDelegate by a keyword event, we are essentially preventing it from getting initialized. In this case, the event OnNameChanged can only be subscribed to or unsubscribed from using += and -= operators.

However, the convention in .NET says, an event does not take arbitrary arguments like two strings, instead it takes two arguments – a System.object type sender and EventArgs  type that encapsulates the arguments sent to it.

public class NameChangedEventArgs : EventArgs
{
     public string OldName {get; set; }
     public string NewName {get; set; }
}

So the event delegate declaration becomes

namespace EventDemo
{
      public delegate void NameChangedDelegate 
                   (object sender, NameChangedEventArgs args);
    
      public event NameChangedDelegate OnNameChanged;
}

Noe lets create a method that can listen to the event OnNameChanged

namespace DelegateDemo
{
     public class DelegateUser
     {
        public void OnNameChanged(object sender, NameChangedEventArgs args)
        {
             Console.WriteLine("The name has been changed from 
                  {0} to {1}", args.OldName, args.NewName);
        }
     } 
}

The code for subscribing to or unsubscribing from the event remains same as above.

Here is how to fire the event by sending correct values in arguments

namespace DelegateDemo
{
   public class Program
   {
       public static void main()
       {
           string oldName = "Hastinapur";
           string newName = "Delhi";
           
           var nameChangedEventArgs = new NameChangedEventArgs
               {
                   OldName = oldName;
                   NewName = newName;
               };
 
           // Firing the event
           OnNameChanged(this, nameChangedEventArgs);
        }
    }
}

// Output
The name has been changed from Hastinapur to Delhi

Summary:

  • Delegate
    • Delegates are a user defined type similar to class or struct
    • They provide a way to hold reference to a method, and can be invoked like a method
    • The signature of delegate declaration must match the signature of method it is supposed to hold reference to
    • A multicast delegate is the one that can hold reference to more than one methods
    • Delegates are foundation of event driven programming and functional programming in C#
  • Event
    • Event is a type of multicast delegate to which method can be added and removed, or subscribed to and unsubscribed from
    • Event cannot be directly instantiated using new keyword
    • To subscribe to and event use C# operator +=
    • To unscubscribe from an event use C# keyword -=
    • By convention an event signature takes two parameters, a type of System.object which is the sender, and a type derived from EventArgs which contains additional parameters to be sent to the event

Toy Exercise:

Simulate the working of train, with and engine and wagons where, wagons are attached to engine and main controls are in engine. i.e. when engine starts all attached wagon get started and when engine breaks all attached wagons break

 

 

Hope this post makes some of fundamentals around delegate and event driven programming clearer. Let me know if I have missed out anything.

Also, I would like to mention the reference to this article is from plural-sight course by K. Scott Allen on C# fundamentals with C# 5 The basic understanding is built upon the concepts explained in there. I have tried to elaborate it through an example