Events, delegates and lambdas in .NET C# part 2: delegates in action

Introduction

In the previous installment of this series we looked at the foundations of delegates, events and event handlers. It’s now time to see them in action.

Demo

Open Visual Studio 2012/2013 and create a new Console application with .NET 4.5 as the underlying framework. I haven’t tested the code with .NET4.0 or .NET 3.5 but I think it should work with those frameworks as well.

In Program.cs add the following delegate declaration just above the Main method:

public delegate void WorldSavedHandler(string saviourName, DateTime dateForNextCatastrophy);

So this is a delegate for when some superhero saves the world for the nth time. It is void, it has a name and accepts two parameters: a string and a date. Note that it’s customary but not mandatory to name delegates with the word ‘Handler’ attached.

Add the following methods that match the delegate signature:

static void WorldSaved(string saviour, DateTime nextTime)
{
	Console.WriteLine(string.Concat("World saved by ", saviour, ", get ready again by ", nextTime));
}

static void WorldSavedAgain(string saviour, DateTime nextTime)
{
	Console.WriteLine(string.Concat("World saved this time by ", saviour, ", get ready again by ", nextTime));
}

In Main you can instantiate a delegate and hook up a callback method as follows:

WorldSavedHandler worldSavedHandler = new WorldSavedHandler(WorldSaved);

As you type the declaration in the editor IntelliSense will indicate that the delegate is expecting a void method that accepts a string and a date. If you try to pass in a method that doesn’t match this signature you’ll get a compile error. Add another delegate declaration to Main:

WorldSavedHandler worldSavedAgainHandler = new WorldSavedHandler(WorldSavedAgain);

We have set up two pipelines: worldSavedHandler which sends its data to WorldSaved and worldSavedAgainHandler which sends its data to WorldSavedAgain. We can invoke the delegates the same way as we normally call a method:

worldSavedHandler("Bruce Willis", DateTime.UtcNow.AddYears(2));
worldSavedAgainHandler("Spiderman", DateTime.UtcNow.AddMonths(3));

Console.ReadKey();

I added the call to ReadKey so that the console window doesn’t just close down.

Run the program and you’ll see the appropriate output in the console window.

Why on earth are we bothering with this? Up to now this looks like some funny way of calling methods. Consider the following method which accepts our delegate type:

static void SaveWorld(WorldSavedHandler worldSaved)
{
	worldSaved("Bruce Willis", DateTime.UtcNow.AddMonths(5));
}

In Main comment out the two delegate calls…:

worldSavedHandler("Bruce Willis", DateTime.UtcNow.AddYears(2));
worldSavedAgainHandler("Spiderman", DateTime.UtcNow.AddMonths(3));

…and add the following instead:

SaveWorld(worldSavedHandler);

This is stating that we need to save the world and we have 2 possible delegates to give this work to. We entrust worldSavedHandler with this task so we pass it in. An analogy from the real world is when a boss decides the software development team needs to build the data access layer and appoints a particular team member to do just that. In other words the boss delegates the job to a particular programmer, i.e. a handler.

We know that worldSavedHandler will call WorldSaved. SaveWorld gets the delegate and will invoke whatever was passed to it. SaveWorld does NOT know which handler the delegate will call. From its point of view it could be any method with the correct signature, so it’s quite a dynamic setup. The delegate could have been passed in from whichever external object to the SaveWorld method. SaveWorld passes in Bruce Willis and now + 3 months to some unknown method.

Run the program and you’ll see that WorldSaved was called. Now, without changing SaveWorld directly, you can modify its outcome just by providing a different delegate:

SaveWorld(worldSavedAgainHandler);

Run the program and WorldSavedAgain was called as expected without changing SaveWorld at all.

Let’s look at multicasting quickly. Comment out the call to SaveWorld in Main and instead add a third delegate declaration:

WorldSavedHandler worldSavedOnceMoreHandler = new WorldSavedHandler(WorldSavedOnceMore);

…where WorldSavedOnceMore looks like this:

static void WorldSavedOnceMore(string saviour, DateTime nextTime)
{
	Console.WriteLine(string.Concat("World saved once again by ", saviour, ", get ready again by ", nextTime));
}

There are different way to build an invocation list. The following chains our three delegates together:

worldSavedHandler += worldSavedAgainHandler;
worldSavedHandler += worldSavedOnceMoreHandler;

worldSavedHandler("Superman", DateTime.UtcNow.AddMonths(3));

You’ll see that all three method handlers were called.

The below is equivalent to the above:

worldSavedHandler += worldSavedAgainHandler + worldSavedOnceMoreHandler;
worldSavedHandler("Superman", DateTime.UtcNow.AddMonths(3));

The following will wipe out the first handler:

worldSavedHandler = worldSavedAgainHandler + worldSavedOnceMoreHandler;
worldSavedHandler("Superman", DateTime.UtcNow.AddMonths(3));

This time only WorldSavedAgain and WorldSavedOnceMore are called.

You’ll also see that each handler is called in the order they were added to the invocation list. This way we can invoke multiple handlers by calling just one delegate, in this case worldSavedHandler.

Returning a value from a delegate

Delegates can return values, they don’t need to be void all the time. Add the following delegate to Program.cs:

public delegate string SomeoneJustShoutedHandler(string who, DateTime when);

Add the following handlers:

static string SomeoneShouted(string who, DateTime when)
{
	return string.Concat(who , " has shouted on ", when);
}

static string SomeoneShoutedAgain(string who, DateTime when)
{
	return string.Concat(who, " has shouted on ", when);
}

static string SomeoneShoutedOnceMore(string who, DateTime when)
{
	return string.Concat(who, " has shouted once more on ", when);
}

…and 3 delegate declarations with the delegate chain:

SomeoneJustShoutedHandler someoneShoutedHandler = new SomeoneJustShoutedHandler(SomeoneShouted);
SomeoneJustShoutedHandler someoneShoutedAgainHandler = new SomeoneJustShoutedHandler(SomeoneShoutedAgain);
SomeoneJustShoutedHandler someoneShoutedOnceMoreHandler = new SomeoneJustShoutedHandler(SomeoneShoutedOnceMore);

someoneShoutedHandler += someoneShoutedAgainHandler + someoneShoutedOnceMoreHandler;
string finalShout = someoneShoutedHandler("The neighbour", DateTime.UtcNow);
Console.WriteLine("Final shout: " + finalShout);

Run the code and you’ll see that it’s only the third string that’s returned. It’s logical as we cannot have multiple return values in C#. You can get the individual return values by calling the delegates separately. However, with an invocation list the last delegate in the list will win.

Adding events

We’ve seen that using delegates can work fine without adding events to the code. However, delegates coupled with events help us raise those events and propagate the event arguments to all listeners in the invocation list.

An event is declared using the ‘event’ keyword in C#. It is also declared with the type of the delegate that raises the event as an event by itself cannot do much. Events are the standard way of providing notifications to all listeners. The data will be supplied using the delegate declared in conjunction with the event. In .NET programming you’ll probably rarely see delegates used without events although it’s certainly possible as we’ve seen above. You also give a name to the event. Here’s an example of an event declaration:

public event WorldSavedHandler WorldHasBeenSaved;

Listeners will be able to sign up with the event and receive a notification when the event has fired. It is equivalent to building the invocation list we saw above. So an event is really nothing but a wrapper around a delegate.

There’s also a more complex way of defining an event using the add/remove accessors:

private WorldSavedHandler WorldHasBeenSaved;
public event WorldSavedHandler WorldSaved
{
	[MethodImpl(MethodImplOptions.Synchronized)]
	add
	{
		WorldHasBeenSaved = (WorldSavedHandler)Delegate.Combine(WorldHasBeenSaved, value);
	}
	[MethodImpl(MethodImplOptions.Synchronized)]
	remove
	{
		WorldHasBeenSaved = (WorldSavedHandler)Delegate.Remove(WorldHasBeenSaved, value);
	}
}

This provides more control over how listeners are added to and removed from the invocation list. You’ll probably not see this kind of event declaration too often. However, if you see it somewhere then you’ll now know what it means.

We have a private delegate of type WorldSavedHandler. Then we see the event declaration that conforms to the example provided above. The add accessor defines how a listener is added to the invocation list. The remove accessor does the opposite. The actions are synchronised to make sure that if different threads perform the same action we don’t run into concurrency issues. The ‘value’ keyword indicates the listener that’s going to be added to the invocation list. The Delegate.Combine method takes the delegate we already have and adds in the new one. The remove accessor works with the Delegate.Remove method that removes ‘value’ from the invocation list.

You might use this type of event declaration if you have extra logic on who and when is allowed to be listed on the invocation list. The access may be limited to some special objects that fulfil certain rules.

Now that we know more about events than we ever wanted to we can create some custom events in our code. We’ll actually do that in the next part.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

One Response to Events, delegates and lambdas in .NET C# part 2: delegates in action

  1. Atilla Selem says:

    Thx for sharing your knowledge so nice & generious. You are doing the best thing for the universe :).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: