Events and Delegates in C# How the hell do they work?

·

23 min read

On my quest into learning C#, events, and delegates really got my head spinning. I reckon it may have been because I wondered why an event would be used when a delegate can be defined by the "publisher class" and then have "subscriber classes or objects" methods added to the said delegate.

Before going further, what is a delegate? it's simply some reference type variable that holds the reference to a method or methods. In other words, it is a "representative" of the said methods. So we can have those methods perform their respective actions for us by simply "calling on" their delegate or representative.

An event is what it is, an event. lol okay, that did not help. An event is simply a way of responding to what happens in our program a.k.a event-driven programming. For example, when we click a button, that act of "clicking" is an event and after that, we can decide to "handle" it with probably a method that has a picture of Thanos pop-up.

If you look at how subscribers subscribe to an event, you may notice your spider-sense tingle with: "hey isn't that how methods are added to delegates ?" If that is your chain of thought then you are not wrong.

However, there are certain key reasons why we don't use delegates to simulate what events do. One of them is to ensure some crazy users of our code do not do stuff with it that we do not permit them to. Please bear in mind that both events and delegates work together when we are in the paradigm of event-driven programming. So they are not in themselves independent of each other.

First, let's create a delegate.

using System;

namespace HashNodeEventsandDelegatesApp
{
    class Program
    {
        public delegate void Restaurant();

        public Restaurant MyRestaurantDelegate;
        static void Main(string[] args)
        {

            Restaurant myRestaurantDel= new Restaurant(Baking);
            myRestaurantDel+= Frying;
            myRestaurantDel();

        }

        static void Baking()
        {
            Console.WriteLine("Baking Bread");
        }

        static void Frying()
        {
            Console.WriteLine("Frying Eggs");
        }
    }
}

//sample output
Baking Bread
Frying Eggs

public delegate void Restaurant(); Is us telling C# that we want to have a delegate named Restaurant which has the following signatures: a return type of void and that it takes no parameters. What this means in effect is any method we intend to add to this delegate must implement or have this signature.

Restaurant myRestaurantDel= new Restaurant(Baking); .Here we declare a new delegate object which we call myRestaurantDel and we instantiate it with our Baking static method. So what does this object do? it simply serves as a reference or pointer to any method or methods we add to it. This way, when we call myRestaurantDel(), it proceeds to run all the methods we added to it.

Do note we did not pass Baking as so Restaurant myRestaurantDel = new Restaurant(Baking());. C# requires us to provide only the method name hence this line Restaurant myRestaurantDel = new Restaurant(Baking);

We created two static methods Baking and Frying. We instantiated our delegate with the Baking method. Later on, we added the Frying method to it also. myRestaurantDel() serves as the "representative" of the Baking and Frying Method.

Now, what if we wanted our customers to know when the restaurant is opened? and let's say we want to send SMS and Emails to the said customers? FYI, We will not be really sending emails and SMS :) just a simulation. lol.

Remember when I said this at the beginning of the article?


"I reckon it may have been because I wondered why an event would be used when a delegate can be defined by the "publisher class" and then have "subscriber classes or objects" methods added to the said delegate. "


What then is the Publisher Class? it is simply the class that notifies its subscribers when an action has happened. It does not know who its subscribers are beforehand but, it is very much happy to have as many subscribers plug into its notification system. The subscriber can now decide to "handle" this notification by any means it deems necessary.

I will be demonstrating using a delegate to simulate the way an event would have worked. After that, we will pick holes in this approach before we finally do the right thing by using events and hopefully see why events are the ideal approach. Alright, let's dig in!

We will be creating a Restaurant Class. This class will have a Kitchen delegate. The Restaurant class will have an OnStartBusiness member method which when called will "notify" all subscribers.

  • Publisher Class: Restaurant.
  • Subscriber Classes: Sms and Email.
using System;

namespace HashNodeEventsandDelegatesApp
{
    public class Restaurant
    {
        public delegate void Kitchen();

        public Kitchen thekitchen;

        static void Main(string[] args)
        {
            var restaurant = new Restaurant();
            var mySMS = new Sms();
            var myEmail = new Email();
            mySMS.Subscribe(restaurant);
            myEmail.Subscribe(restaurant);
            restaurant.OnStartBusiness();


        }

        public void OnStartBusiness()
        {
            if (thekitchen != null)
            {
                thekitchen();
            }
        }

    }

    class Sms
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += new Restaurant.Kitchen(SendSMS);
        }

        public void SendSMS()
        {
            Console.WriteLine("sending SMS");
        }
    }

    class Email
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += new Restaurant.Kitchen(SendEmail);
        }

        public void SendEmail()
        {
            Console.WriteLine("sending EMail");
        }
    }
}

/*sample output

sending SMS
sending EMail

*/

Now, let's say for some reason our Email class mistakenly assigned ("=") rather than "subscribe" ("+=") to our delegate?

using System;

namespace HashNodeEventsandDelegatesApp
{
    public class Restaurant
    {
        public delegate void Kitchen();

        public Kitchen thekitchen;

        static void Main(string[] args)
        {
            var restaurant = new Restaurant();
            var mySMS = new Sms();
            var myEmail = new Email();
            mySMS.Subscribe(restaurant);
            myEmail.Subscribe(restaurant);
            restaurant.OnStartBusiness();


        }

        public void OnStartBusiness()
        {
            if (thekitchen != null)
            {
                thekitchen();
            }
        }

    }

    class Sms
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += new Restaurant.Kitchen(SendSMS);
        }

        public void SendSMS()
        {
            Console.WriteLine("sending SMS");
        }
    }

//assigns instead of subscribing
    class Email
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen = new Restaurant.Kitchen(SendEmail);  //wrongly assigns
        }

        public void SendEmail()
        {
            Console.WriteLine("sending EMail");
        }
    }
}

/*sample output

sending EMail

*/

When we run our code, we will get only sending Email as the sample output because now EMail class has assigned to our delegate field in the Kitchen class instead of subscribing to it.

Another issue that can arise is that the delegate "public delegate void Kitchen();" can be called directly in our Main method without running the StartBusiness() notifier. This defeats the intention of the Restaurant Class desire to have the methods encapsulated in the Kitchen delegate to be run only when the StartBusiness() event is fired or occurs.

using System;

namespace HashNodeEventsandDelegatesApp
{
    public class Restaurant
    {
        public delegate void Kitchen();

        public Kitchen thekitchen;

        static void Main(string[] args)
        {
            var restaurant = new Restaurant();
            var mySMS = new Sms();
            var myEmail = new Email();
            mySMS.Subscribe(restaurant);
            myEmail.Subscribe(restaurant);
            //restaurant.OnStartBusiness();
            restaurant.thekitchen += () =>
            {
                Console.WriteLine("I am calling the Kitchen delegate without the StartBuisness() event ");
            };  // I  used a lambda expression to add an anonymous method.

            restaurant.thekitchen();
        }

        public void OnStartBusiness()
        {
            if (thekitchen != null)
            {
                thekitchen();
            }
        }

    }

    class Sms
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += new Restaurant.Kitchen(SendSMS);
        }

        public void SendSMS()
        {
            Console.WriteLine("sending SMS");
        }
    }

    class Email
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += new Restaurant.Kitchen(SendEmail);
        }

        public void SendEmail()
        {
            Console.WriteLine("sending EMail");
        }
    }
}

/*sample output

sending SMS
sending EMail
I am calling the Kitchen delegate without the StartBuisness() event

*/

As we can see, Main( ) has gone around through the back door and invoked the methods itself rather than through the StartBusiness() event.

So, the issue now is how can you, as the designer of the Restaurant class, see to it that no one calls the delegated method directly? Make the delegate private? but then it won’t be possible for subscribers to register with your delegate at all. This is where events come in.

To use events, we first define a delegate. Next, we define an event based on that delegate. Finally, we raise the event. If there are subscribers to the event, they get notified.

We are going to approach Using events in two ways. First, we are going to use events without passing down any data or state to our subscribers. Finally, we will use events to pass data, maybe a client's name, to the subscribers. Awesome, let's dig in.

using System;

namespace HashNodeEventsandDelegatesApp
{
    public class Restaurant
    {
        public delegate void Kitchen(object source, EventArgs args);

        public event Kitchen thekitchen;

        static void Main(string[] args)
        {
            var restaurant = new Restaurant();
            var mySMS = new Sms();
            var myEmail = new Email();
            mySMS.Subscribe(restaurant);
            myEmail.Subscribe(restaurant);
            restaurant.OnStartBusiness();

        }

        public void OnStartBusiness()
        {
            if (thekitchen != null)
            {
                thekitchen(this, EventArgs.Empty);
            }
            else
            {
                Console.WriteLine("No subscribers yet");
            }
        }

    }

    class Sms
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += new Restaurant.Kitchen(SendSMS);
        }

        public void SendSMS(object source, EventArgs args)
        {
            Console.WriteLine("sending SMS");
        }
    }

    class Email
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += new Restaurant.Kitchen(SendEmail);
        }

        public void SendEmail(object source, EventArgs args)
        {
            Console.WriteLine("sending EMail");
        }
    }
}

/* sample output
sending SMS
sending EMail
*/

We can do the above code in a much easier way by using the built-in delegate EventHandler delegate.

Normally, any event should include two parameters: the source of the event and event data. Use the EventHandler delegate for all events that do not include event data. Use EventHandler delegate for events that include data to be sent to handlers.


using System;

namespace HashNodeEventsandDelegatesApp
{
    public class Restaurant
    {
        //public delegate void Kitchen(object source, EventArgs args);

        public event EventHandler thekitchen;

        static void Main(string[] args)
        {
            var restaurant = new Restaurant();
            var mySMS = new Sms();
            var myEmail = new Email();
            mySMS.Subscribe(restaurant);
            myEmail.Subscribe(restaurant);
            restaurant.OnStartBusiness();

        }

        public void OnStartBusiness()
        {
            if (thekitchen != null)
            {
                thekitchen(this, EventArgs.Empty);
            }
            else
            {
                Console.WriteLine("No subscribers yet");
            }
        }

    }

    class Sms
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += SendSMS; //easier way to add the event handler
        }

        public void SendSMS(object source, EventArgs args)
        {
            Console.WriteLine("sending SMS");
        }
    }

    class Email
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += SendEmail; //easier way to add the event handler
        }

        public void SendEmail(object source, EventArgs args)
        {
            Console.WriteLine("sending EMail");
        }
    }
}

Running the above will still give us the same output as before.

Now let's say we want to send down the name of the client. We can approach it as follows:

using System;

namespace HashNodeEventsandDelegatesApp
{

    public class RestuarantClient : EventArgs
    {
        public string Client { get; set; }
    }
    public class Restaurant
    {
        //public delegate void Kitchen(object source, EventArgs args);

        public event EventHandler<RestuarantClient> thekitchen;

        static void Main(string[] args)
        {
            var restaurant = new Restaurant();
            var mySMS = new Sms();
            var myEmail = new Email();
            mySMS.Subscribe(restaurant);
            myEmail.Subscribe(restaurant);
            restaurant.OnStartBusiness("Mr Orlando");

        }

        protected virtual void OnStartBusiness(string ClientName)
        {
            if (thekitchen != null)
            {
                thekitchen(this, new RestuarantClient () {Client = ClientName});
            }
            else
            {
                Console.WriteLine("No subscribers yet");
            }
        }

    }

    class Sms
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += SendSMS;
        }

        public void SendSMS(object source, RestuarantClient client)
        {
            Console.WriteLine($"sending SMS to {client.Client}");
        }
    }

    class Email
    {
        public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += SendEmail;
        }

        public void SendEmail(object source, RestuarantClient client)
        {
            Console.WriteLine($"sending EMail to {client.Client}");
        }
    }
}

/* sample output

sending SMS to Mr Orlando
sending EMail to Mr Orlando

*/

public event EventHandler<RestuarantClient> thekitchen; In this line, we are simply using the generic delegate to ensure that the type of data we are passing down is of type RestuarantClient.

public void OnStartBusiness(string ClientName) takes a string called clientName as its only parameter. That parameter is then used to instantiate a new object of type RestuarantClient as seen in this line: thekitchen(this, new RestuarantClient () {Client = ClientName});. This allows any subscriber to have access to the client name.

Lest I forget, you may wonder where is the "subscription" happening? Well, this is where:

public void Subscribe(Restaurant theRestaurant)
        {
            theRestaurant.thekitchen += SendEmail;
        }

Here, we passed an object of type Restaurant. We then use that to access our field in the Restaurant class which is an event called thekitchen by doing this theRestaurant.thekitchen.

We proceeded to "subscribe" by adding our event handler housed in our Email class which we called SendEmail by doing this theRestaurant.thekitchen += SendEmail;.

In effect, when we did this:


 static void Main(string[] args)
        {
            var restaurant = new Restaurant();
            var mySMS = new Sms();
            var myEmail = new Email();
            mySMS.Subscribe(restaurant);
            myEmail.Subscribe(restaurant);
            restaurant.OnStartBusiness("Mr Orlando");

        }

We were calling our subscribe method which simply added the eventhandler sendEmail for instance to the thekitchen event of our publisher class. This way, when we run restaurant.OnStartBusiness("Mr Orlando"); we in effect trigger all the methods or event handlers added to our theKitchen event.

We will stop here for now. hopefully, this was helpful.