A simple c# events and delegates tutorial

By | September 11, 2017

This c# events and delegates tutorial has a simple aim: Make it easier for you to work with events in .NET. There will be no deep dive. Everything I discuss below about c# events and delegates has one purpose only: To enable you to start using events with confidence. Many people find it difficult to get their head around c# events and delegates. This post hopefully will clear the air around this once and for all.

WHY

Events are everywhere in .NET world. They are the most common way of implementing the observer pattern in .NET world. You simply cannot escape them. Especially if you are making UI’s using technologies like UWP.

HOW

I will discuss only those parts about c# events and delegates which will make you comfortable working with .NET events.

There are two main characters in the story of c# events and delegates. First one is called Delegate.
Delegate is what function pointer is in the world of C++. Only better behaved.
It holds reference to a method. Or multiple methods. More on that later.

The best way to understand delegates is to think of them as a class. Just like class we can declare the delegate anywhere with whatever access level required. And to use a delegate you have to have an instance of the delegate class.

Here is an example of a delegate declaration

delegate int AdditionDelegate(int a, int b);

This delegate can hold reference to function which takes two integers and returns their sum.
Suppose you have a method

int AddTwoNumbers(int a,int b)
{
    return a+b;
}

Below is example of a delegate instance creation and initialisation.

AdditionDelegate demoDelegate = new AdditionDelegate(AddTwoNumbers);

.NET however allows a shorthand.

AdditionDelegate demoDelegate = AddTwoNumbers

It works because the c# compiler can just examine the parameters taken and the return type of the function AddTwoNumbers. Then it can decide that an instance of delegate need to be created and initialised. It does all that in background.

And you can then invoke the function using delegate

var result = demoDelegate(5,5);

Action and Func delegates
Designers of .NET introduced two predefined delegates.

Action is a delegate which can be used for referencing functions which
1. Do not return any values.
2. Take either no parameters, a single parameter all the way till 16 parameters.

Func is a delegate which can be used for referencing functions which
1. Return value.
2. Take either no parameters, a single parameter all the way till 16 parameters.

So as an example

AdditionDelegate demoDelegate = new AdditionDelegate(AddTwoNumbers);

becomes

Func demoDelegate = AddTwoNumbers;

Note that there is no need to declare the delegate AdditionDelegate as we used to to before. We are using something which .NET is providing us.

Func and Action were created for supporting functional programming in c#. Somehow they found a role in c# events and delegates saga.

Multicast delegates
Remember I said that a delegate can reference more than one method. Only condition is that the function signature should match. Such a delegate is called multicast delegate. Here is a sample.

AdditionDelegate demoDelegate = AddTwoNumbers;
demoDelegate += AddTwoMoreNumbers;
demoDelegate += AddStillTwoMoreNumbers;

Needless to say, -= syntax is also supported to remove the function references.

When this delegate is invoked it will invoke each of the method registered in it. However order in execution is not guaranteed guaranteed by .NET. So do not depend on that !!

Note that compiler does not prevent you from registering value returning functions to appropriate delegate. But it is a bad idea to do so. The value you get is the value returned by the last function which gets invoked. Use multicast delegates with functions that return void.

Anonymous functions and delegates
If you thought that I am done then look below.

Func demoDelegate = AddTwoNumbers;

AddTwoNumbers is a trivial function that just adds two numbers and returns the value. Do we really need a function to provide such a simple operation? It is small and is getting used here only. No need to keep it around. Enter anonymous functions. It has no name. It is just a block of code.

Func demoDelegate = delegate(int x,int y) {return x+y;}

c# events and delegates

Lambda’s and delegates
Instead of anonymous functions we can use lambda. Now these are even more succinct.

Func demoDelegate = delegate(int x,int y) {return x+y;}

becomes

Func demoDelegate = (x,y) => x+y;

To put in simple terms, we have defined a lambda on the right side. It is taking two parameters which are passed to the code body on the other side of => and the result is then assigned to the delegate. Lambdas (along with Func and Action) actually are the way functional programming is done in c# land. Maybe I will write a post on this someday.

If it takes no parameters then the lambda is like

()=> 3

This one does not take a parameter and returns 3.
If it takes only single parameter then

x => x+100;

This one increments the value of the passed variable by 100 and returns the result.
And if it takes two parameters then

(x,y)=>
{
    //something
    return x+y;
}

Anyways I digress.

Finally the Events
The story here is long and complicated and I will not go into it. I will go straight to the way .NET wants us to write events.

Events are delegates under hood and .NET has defined a delegate just for this express purpose

public delegate void EventHandler(object sender, TEventArgs e);

See how this delegate will work with function which take an object and and an object of a class derived from EventArgs. Wait EventArgs?

.NET has defined a class called EventArgs. You have to derive a class from this class. You use this class to pack in any information about the event which you want to pass on to the functions handling the event.

Let me create a class which will do exactly the same.

public class DownloadCompleteEventArgs : EventArgs
    {
        public DownloadCompleteEventArgs(string fileName)
        {
            FileName = fileName;
        }

        public string FileName { get; }
    }

So how we declare an event? Using the delegate as it is?
c# events and delegates

This is how we declare instance of an event.

public event EventHandler FileDownloaded;

Notice the keyword event. It changes the behaviour of the delegate just like public keyword modifies the possible usage of a field of a class. More on this later.

Now that the event instance has been created it is time to put it in action. I think the best option is to list the whole file.

using System;

namespace DelegateDemo
{
    public class FileDownloader
    {
        private readonly string _fileName;

        public event EventHandler FileDownloaded;

        public FileDownloader(string fileName)
        {
            _fileName = fileName;
        }
        public void StartFileDownload()
        {
            //Assume the file downloaded successfully
            //Raise event to subscribers notifying them
            RaiseFileDownloadedEvent(new DownloadCompleteEventArgs(_fileName));
        }

        protected virtual void RaiseFileDownloadedEvent(DownloadCompleteEventArgs eventArgs)
        {
            FileDownloaded?.Invoke(this, eventArgs);
        }
    }
}

Line 09: We declare an instance of the event provided by Microsoft and call it FileDownloaded.
Line 15: Function which will be called by the user of the program to download the file. Once completed it will call method RaiseFileDownloadedEvent to raise the event that downloading has finished.
Line 19: RaiseFileDownloadedEvent is called. Note how we are packaging information regarding the event using the DownloadCompleteEventArgs.
Line 24: We check if someone has subscribed to FileDownloaded event in a thread safe manner (C# 6 syntax). If yes then we raise it via invoke. Done.

Now on to the client code Reader.cs.

using System;

namespace DelegateDemo
{
    public class Reader
    {
        public Reader(FileDownloader fileDownloader)
        {
            fileDownloader.FileDownloaded += OnFileDownloaded;
        }

        private void OnFileDownloaded(object sender, DownloadCompleteEventArgs e)
        {
            Console.WriteLine("\nThe book '" + e.FileName + "' has been read by Reader A");
        }
    }
}

Line 09: The client class subscribes to the event of the class FileDownloader. Method OnFileDownloaded will run when this event is raised by the FileDownloader class.
Line 14: We just display some text to show that OnFileDownloaded indeed ran. See how the information from the event argument is getting used.

This is the main code Program.cs

using System;

namespace DelegateDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            FileDownloader fileDownloader = new FileDownloader("How to win a lottery");
            Reader clientA = new Reader(fileDownloader);
            fileDownloader.StartFileDownload();
        }
    }
}

StartFileDownload starts the file download and once that is done an event is raised. That event is processed by the client as we saw.
On running it we get:
c# events and delegates

Here is the last thing about c# events and delegates.
Many a times you will see a variation of event handler registration in the clients.
This line in Reader.cs becomes

fileDownloader.FileDownloaded += OnFileDownloaded;

becomes

fileDownloader.FileDownloaded += (sender, eventArgs)=>{Console.WriteLine("\nThe book '" + eventArgs.FileName + "' has been read by Reader A");};

Now you do not need OnFileDownloaded method to be written in the client. This is preferable if the logic in the handler is simple. Otherwise I stick to having a separate method.

And that is it. You are done. There is loads and loads of stuff I can talk about c# events and delegates but this is more than enough to get you started.

We are at the end of this tutorial on c# events and delegates. But in case you are curious here is what the keyword event does.

Replace

fileDownloader.FileDownloaded += OnFileDownloaded;

with this

fileDownloader.FileDownloaded = null;

You will get an error saying that

The event 'FileDownloader.FileDownloaded' can only appear on the left side of += or -= (except when used from within the type 'FileDownloader')

If you think about it then it makes sense. See how I am passing an instance of FileDownloader to the constructor of the client. If there are more than one clients then each of them gets same instance of FileDownloader. If one client sets the FileDownloader.FileDownloaded to null then it essentially is wiping out the subscriptions which other clients have made. Not very goood. Hence the keyword event. It puts restrictions around the delegate it is wrapping and makes it safer.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.