Events, delegates and lambdas in .NET C# part 4: subscribing to events
February 24, 2014 Leave a comment
Introduction
We saw in the previous post how to add delegates, events and event handlers to our SuperHero class. We also discussed that the standard signature for a delegate in .NET accepts the sender as an objects and another object that inherits from the EventArgs class. You may of course define your delegate as you like but consumers of your class will be expecting the standard signature.
The missing piece now is to wire up those elements. The key is to understand how to add an element to the invocation list. Then as the event fires the elements – methods – will be called one after the other in that list.
Demo
Open the project we’ve been working on in this series. The goal is to create a SuperHero object and subscribe to its events. You can imagine that Program.cs is the police chief who sends out a hero to save the world but wants him/her to report the progress and outcome of the work done.
In Main declare a new SuperHero:
SuperHero superman = new SuperHero();
There’s a number of ways you can subscribe to the events of the superhero. Start typing “superman.” in the editor and IntelliSense will list the available events denoted by a lightning bolt. You add elements to the invocation list with the “+=” operator. So type…
superman.WorldSaved +=
…in the editor and you’ll see that IntelliSense is trying to help you by automatically suggesting a method that will be created for you and which matches the delegate signature. Press TAB twice in order for this to happen. You should have the following method in Program.cs:
static void superman_WorldSaved(object sender, WorldSavedEventArgs e) { throw new NotImplementedException(); }
This is called delegate inference. You didn’t have to new up a delegate yourself. The compiler will do it for you in the background.
Another way to get to the same result is to new up a WorldSavedHandler delegate and pass in the method that will be added to the invocation list:
superman.WorldSaved += new DelegatesIntro.WorldSavedHandler(superman_WorldSaved);
You can test to pass in a method that does not match the delegate signature: Visual Studio will complain immediately.
You can do it either way, it doesn’t make any difference to the outcome. The first option is probably more convenient as Visual Studio is guaranteed to create a method with the correct signature and you can save some code and time.
Modify the method to show some message on the console window:
static void superman_WorldSaved(object sender, WorldSavedEventArgs e) { StringBuilder superHeroMessageBuilder = new StringBuilder(); superHeroMessageBuilder.Append("Superhero reporting progress! Name: ") .Append(e.SaviourName).Append(", has been working for ").Append(e.WorkHasBeenOngoingHs) .Append(" hours, ").Append(" date of next occasion: ").Append(e.DateOfNextCatastrophy); Console.WriteLine(superHeroMessageBuilder.ToString()); }
The next step is to call on the superhero to finally start working:
superman.SaveTheWorld("Superman", DateTime.UtcNow.AddMonths(3));
Run the code and you’ll see that Superman reports the progress as expected. So you can see that this is kind of a nice way to get multiple values returned by a void method.
Let’s also subscribe to the other event of the superhero:
superman.WorldSavingCompleted += superman_WorldSavingCompleted;
…where superman_WorldSavingCompleted looks like this:
static void superman_WorldSavingCompleted(object sender, WorldSavingCompletedEventArgs e) { StringBuilder superHeroMessageBuilder = new StringBuilder(); superHeroMessageBuilder.Append("Superhero has saved the world! Name: ").Append(e.Saviour) .Append(", time spent saving the world: ").Append(e.HoursItTookToSaveWorld).Append(", message from the superhero: ") .Append(e.MessageFromSaviour); Console.WriteLine(superHeroMessageBuilder.ToString()); }
Run the code and you’ll see that the correct methods are called when the events fire.
If you want to remove a method from the invocation list then you can use the “-=” operator. If you insert the following – admittedly senseless – code, then you won’t see when the superhero has finished saving the world:
superman.WorldSavingCompleted += superman_WorldSavingCompleted; superman.WorldSavingCompleted -= superman_WorldSavingCompleted;
Anonymous methods
So far we have declared the elements that react to events as standalone methods. There’s nothing wrong with that at all, but it’s not the only way. As an alternative you can embed the actions in something called an anonymous method which can only be used at the source of the subscription, i.e. where the “+=” operator is used. An anonymous method has no name and may look quite strange at first. You may have heard of “lambdas” before, but they are not the same thing as anonymous methods. They are similar features but still not quite the same. We’ll be looking into lambdas in a later post.
Anonymous methods are defined by the “delegate” keyword. It is followed by the input parameter declarations and finally the method body in curly braces. You close the whole statement with a semi-colon ‘;’.
So we can rewrite…
superman.WorldSavingCompleted += superman_WorldSavingCompleted; static void superman_WorldSavingCompleted(object sender, WorldSavingCompletedEventArgs e) { StringBuilder superHeroMessageBuilder = new StringBuilder(); superHeroMessageBuilder.Append("Superhero has saved the world! Name: ").Append(e.Saviour) .Append(", time spent saving the world: ").Append(e.HoursItTookToSaveWorld).Append(", message from the superhero: ") .Append(e.MessageFromSaviour); Console.WriteLine(superHeroMessageBuilder.ToString()); }
…as follows:
superman.WorldSavingCompleted += delegate(object sender, WorldSavingCompletedEventArgs e) { StringBuilder superHeroMessageBuilder = new StringBuilder(); superHeroMessageBuilder.Append("Superhero has saved the world! Name: ").Append(e.Saviour) .Append(", time spent saving the world: ").Append(e.HoursItTookToSaveWorld).Append(", message from the superhero: ") .Append(e.MessageFromSaviour); Console.WriteLine(superHeroMessageBuilder.ToString()); };
We declare that whatever happens in case the event is fired, it is done right at the source. Note that this method can only be used “here and now” and not be called from anywhere else: it cannot be reused in other words. You may take this approach in case the method body is very simple and don’t want to write a dedicated method for it. In case the method body gets too long then you might want to break it out into a separate method.
We’ll start discussing lambdas, Func of T and Action of T in the next post.