Events, delegates and lambdas in .NET C# part 3: delegates with events in action
February 20, 2014 2 Comments
Introduction
In the previous part of this series we looked at some examples of delegates. We also discussed the basics of events in .NET. We’re now ready to see some custom events in action.
Demo
Open the project we’ve been working on in this series. Add a new class to the project called SuperHero. Add the following method stub to SuperHero.cs:
public void SaveTheWorld(string saviourName, DateTime dateForNextCatastrophy) { }
Also, add the same delegate as we had before in Program.cs:
public delegate void WorldSavedHandler(string saviourName, DateTime dateForNextCatastrophy);
You can also put this is a separate class, it’s up to you.
Let’s say that an external class, such a police chief calls SuperHero to save the world and wants to monitor the progress. SaveTheWorld is void so we cannot read out anything from this method. However, we can raise events. We now know how to declare events so let’s add one to SuperHero.cs:
public event WorldSavedHandler WorldSaved;
The police chief will want to know when the world has been saved, i.e. the superhero’s work has completed. We can add an event handler for that. Add the following code just below the event declaration:
public event EventHandler WorldSavingCompleted;
EventHandler is the built-in .NET event handler that may look familiar to you from Windows UI programming. It is a delegate that does not return anything, accepts an object – the sender – and an EventArgs argument. So this is just a short-hand solution for a case where we don’t have any specialised event arguments. We only want to notify all listeners that the work has been completed. Note that EventHandler has a generic form as well: EventHandler of type T : EventArgs. Use this version if you want to specify a more specialised EventArgs class than the default empty EventArgs:
public event EventHandler<WorldSavingCompletedEventArgs> WorldSavingCompleted;
Then when you raise this event you’ll have to provide WorldSavingCompletedEventArgs to it instead of just EventArgs. Later on we’ll see an example of this.
We have now declared the events. The next step is to raise them. There are basically two ways to do that. One is to call an event like you would call a method:
if (WorldSaved != null) { WorldSaved(saviourName, dateForNextCatastrophy); }
Another way is to extract the delegate from the event and invoke it:
WorldSavedHandler handler = WorldSaved as WorldSavedHandler; if (handler != null) { handler(saviourName, dateForNextCatastrophy); }
Why do we check for a null value? It’s because if no other object has signed up with the event, i.e. there’s nothing in the invocation list, then the event will be null and you’ll get a null pointer exception if you try to raise it. It is the same as trying to call a method of an object that has not been initialised.
A common practice is to raise the event in a dedicated method and give that method a special name: ‘On’ + the name of the event, kind of like the familiar “OnClick” and “OnSelectedItemChanged” etc. methods. This is not a must for the event to work, only a possible way to organise your code:
public void SaveTheWorld(string saviourName, DateTime dateForNextCatastrophy) { OnWorldSaved(saviourName, dateForNextCatastrophy); } private void OnWorldSaved(string saviourName, DateTime dateForNextCatastrophy) { if (WorldSaved != null) { WorldSaved(saviourName, dateForNextCatastrophy); } }
Let’s say that the police chief wants to monitor the superhero’s progress every hour. We’ll also hard code the duration of the superhero’s work to 4 hours to make this simple. So we’ll raise a WorldSaved event every hour to report some progress. At the end of the 4 hours we’ll raise a WorldSavingCompleted event to tell the world that the superhero has finished:
public void SaveTheWorld(string saviourName, DateTime dateForNextCatastrophy) { int hoursToSaveTheWorld = 4; for (int i = 0; i < hoursToSaveTheWorld; i++) { OnWorldSaved(i + 1, saviourName, dateForNextCatastrophy); Thread.Sleep(1000); } OnWorldSavingCompleted(); } private void OnWorldSaved(int hoursPassed, string saviourName, DateTime dateForNextCatastrophy) { if (WorldSaved != null) { WorldSaved(string.Concat(saviourName, " has been working for ", hoursPassed, " hour(s)"), dateForNextCatastrophy); } } private void OnWorldSavingCompleted() { if (WorldSavingCompleted != null) { WorldSavingCompleted(this, EventArgs.Empty); } }
So we report every hour worked by the superhero. In the OnWorldSavingCompleted method we raise the default .NET event which accepts the sender as an object and an EventArgs class. The sender of the event is ‘this’, i.e. the current SuperHero object. We don’t have any data to transmit to the listeners so we use the EventArgs.Empty convenience method. We’ll see later on how to send your data with your custom EventArgs object.
This is quite a nice way of getting sort of multiple return values out of void methods. Indeed, all listeners will be notified of the events raised by the superhero.
Custom event arguments
We’ve seen two ways to create delegates in this post: directly with the ‘delegate’ keyword and by using the built-in EventHandler class that is a wrapper around a delegate. Before we see how to subscribe to these events from the outside we need know how to pass custom event arguments.
The usual and standard way in .NET to create events is by specifying a method signature where the handling method will accept the sender as an object and an EventArgs. You must have seen that before if you did any Windows GUI development. We’ve also seen an example of that in OnWorldSavingCompleted(). However, that is kind of limiting as the default EventArgs is – almost – empty, you cannot supply your own messages to it.
Creating a custom EventArgs is very easy: create a class and inherit from the EventArgs object. You are then free to put an as many properties as you wish. You don’t need to worry about extending a parameter list of some delegate, you can just add new properties to your event args class. Add the following class to the project:
public class WorldSavingCompletedEventArgs : EventArgs { public string Saviour { get; set; } public DateTime TimeForNextCatastrophy { get; set; } public int HoursItTookToSaveWorld { get; set; } public string MessageFromSaviour { get; set; } }
The naming convention is to take the name of the event and attach ‘EventArgs’ to it. All the data that needs to be wired to the listeners is now encapsulated within this event args object. It is easy to extend and the properties are easily extracted from it.
Let’s clean up the WorldSavedHandler delegate. Add the following custom event args:
public class WorldSavedEventArgs : EventArgs { public string SaviourName { get; set; } public DateTime DateOfNextCatastrophy { get; set; } }
The WorldSavedHandler will take the following form:
public delegate void WorldSavedHandler(object sender, WorldSavedEventArgs e);
You’ll get compiler errors because we’ve changed the signature of the handler, we’ll correct it in a bit. As hinted at above the EventHandler object has a generic form to specify the type of the event handler. An advantage of using the EventHandler class will ensure that you’re sticking to the standard event signature. Modify the WorldSavingCompleted event handler to the following:
public event EventHandler<WorldSavingCompletedEventArgs> WorldSavingCompleted;
We can keep both examples, i.e. the delegate and the EventHandler in this demo project so that you can see both approaches, but in practice it’s more convenient to just stick to EventHandler and EventHandler of T. Using the EventHandler object saves having a separate delegate. Use the delegate-event-pair approach if you need access to the delegate on its own, i.e. without the matching event. Let’s clean up our code so that it compiles:
public delegate void WorldSavedHandler(object sender, WorldSavedEventArgs e); public class SuperHero { public event WorldSavedHandler WorldSaved; public event EventHandler<WorldSavingCompletedEventArgs> WorldSavingCompleted; public void SaveTheWorld(string saviourName, DateTime dateForNextCatastrophy) { int hoursToSaveTheWorld = 4; for (int i = 0; i < hoursToSaveTheWorld; i++) { OnWorldSaved(i + 1, saviourName, dateForNextCatastrophy); Thread.Sleep(1000); } OnWorldSavingCompleted(hoursToSaveTheWorld, "Yaaay!", saviourName, dateForNextCatastrophy); } private void OnWorldSaved(int hoursPassed, string saviourName, DateTime dateForNextCatastrophy) { if (WorldSaved != null) { WorldSavedEventArgs e = new WorldSavedEventArgs() { DateOfNextCatastrophy = dateForNextCatastrophy , SaviourName = saviourName , WorkHasBeenOngoingHs = hoursPassed }; WorldSaved(this, e); } } private void OnWorldSavingCompleted(int totalHoursWorked, string message, string saviour, DateTime timeOfNextCatastrophy) { if (WorldSavingCompleted != null) { WorldSavingCompletedEventArgs e = new WorldSavingCompletedEventArgs() { HoursItTookToSaveWorld = totalHoursWorked , MessageFromSaviour = message , Saviour = saviour , TimeForNextCatastrophy = timeOfNextCatastrophy }; WorldSavingCompleted(this, e); } } }
You’ll see the revised delegate and event handler definitions. They follow the sender/eventargs convention. You also see how easy it is to wire data through the custom event args to the listeners.
Now the SuperHero class is ready to work and make announcements. But no-one is listening yet to catch those event notifications. We’ll see how to do just that in the next post.
Nice article!
But it’s not common to use if(WorldSaved != null).
It’s better to use two steps because of race conditions on invoking your eventhandler.
So do:
var handler = WorldSaved;
if(handler != null)
….
Very useful article. Just one note.
This way of initialization:
public event MyClickHandler Click = delegate {}; // add empty delegate!
give us option to skip null check
if (Click != null) // Unnecessary!
Click(this, “foo”);