25. September 2018
Simple use of Events
I’m working a lot with events and delegate at work these days so I thought I should take a better look at them and try to explain a simple usage of events as best I can.
For this post I’m just going to show you a simple usage of events and maybe go deeper down the rabbit hole with delegates later in another post (I already started a post I have been working on for 3 months but I´m not ready to publish that one).
Events
I’m going to bring up one place where events shine.
Its when you have a class (A) that you don’t want/can’t change (to do something) but you still need things to happen (in class B) when something in that class (A) is triggered.
There are all kinds of ways you could have done this if you would just change the class (A). You could e.g return some values and based off of them you could do something. That is if you don’t just add your code to the class (A).
But sometimes you just can’t change the code (you don’t own it, its in a critical system that you are now allowed to change etc.)
This all sounds little bit messy?
Shall we try to do this more elegantly?
Example
Say we have a factory (a real one) that needs to process something and when it is done we want to receive a notification, lets say an email.
The class with the event
First we look at our processing class.
It has 2 vital things
- delegate and an event
- Method that “fires” the event.
public class ProcessingFactory
{
public event EventHandler ProcessingDone;
// The following two lines are the same as the ONE above
//public delegate void ProcessingDoneEventHandler(object source, EventArgs args);
//public event ProcessingDoneEventHandler ProcessingDone;
public void StartProcessing()
{
Console.WriteLine("Starting processing of material");
Thread.Sleep(2000);
//Processing has now finished so we fire up the event
OnProcessingDone();
}
// Use a protected virtual method to raise each event (AV1225)
// This is a conventions in the Event Design guidelines
// https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms229011(v=vs.100)
protected virtual void OnProcessingDone()
{
if (ProcessingDone != null)
{
ProcessingDone(this, EventArgs.Empty);
}
}
}
So basically when the processing has stopped (StartProcessing() has finished running) the event is fired up.
When that happens everything that has registered to this event will get notified and run.
The subscribers
To let something happen (because of the event firing), something has to have subscribed to the event.
Lets say we have a System class that oversees the factory and starts the processing.
And when the factory has started (and finished) processing we want to get an email letting us know.
First we need a class that will handle communications to our mail server.
That class needs a method with the same signature as the delegate in the ProcessingFactory class.
public class EmailSender
{
public void OnProcessingDone(object source, EventArgs e)
{
Console.WriteLine("Send notification,through EMAIL, that the factory finished processing");
}
}
And here you see how we hook the EmailSender into the event when the ProcessingDone has fired.
public class System
{
public ProcessingFactory Factory = new ProcessingFactory();
public SystemSettings()
{
var emailSender = new EmailSender();
// Register for when the Processing is done
// By doing this you can hook your implementation to the firing of ProcessingDone
// in the ProcessingFactory class WITHOUT going into it and changing it!
// That is where the strength lies!
Factory.ProcessingDone += emailSender.OnProcessingDone;
}
public void StarFactory()
{
Factory.StartProcessing();
}
}
Now you are wondering “why didn’t you just add the send mail functionality in to the Factory class?
Because of the S in SOLID where the S stands for single responsibility principle.
We don’t want the factory class to do anything else than just process the material in the factory.
New feature request “We need Slack and snailMail!”
Now it will hopefully be obvious why using events can be a good design.
We need to add the alerts to Slack (through their GraphQL1) and then in snail-mail through the Post Office WCF service.
What do you do?
Easy, just add new senders and let them subscribe to the event. No need to add this code straight in to the SystemSettings or ProcessingFactory classes.
SlackSender
public class SlackSender
{
public void OnProcessingDone(object source, EventArgs e)
{
Console.WriteLine("Send notification,through SLACK, that the factory finished processing");
}
}
SnailMailSender
public class SnailMailSender
{
public void OnProcessingDone(object source,EventArgs e)
{
Console.WriteLine("Send notification,through SNAILMAIL, that the factory finished processing");
}
}
Now the only thing missing is to register the sender classes for the event.
public class SystemSettings
{
public ProcessingFactory Factory = new ProcessingFactory();
public SystemSettings()
{
var emailSender = new EmailSender();
var slackSender = new SlackSender();
var snailMailSender = new SnailMailSender();
// Register 3 methods for when the Processing is done
// By doing this you can hook your implementation to when the ProcessingDone is fired
// in the ProcessingFactory class WITHOUT going into it and changing it!
// Here is where the strength of events lie!
Factory.ProcessingDone += emailSender.OnProcessingDone;
Factory.ProcessingDone += slackSender.OnProcessingDone;
Factory.ProcessingDone += snailMailSender.OnProcessingDone;
// etc. to add other methods that should fire up when ProcessinDone fires.
}
public void StarFactory()
{
Factory.StartProcessing();
}
}
Does this all make sense to you?
Sending data with the event
What you could (but shouldn’t) do is the following.
public event EventHandler<string> ProcessingDoneWithStringMessage;
public event EventHandler<bool> ProcessingDoneWithBool;
public event EventHandler<EmployeeFound> ProcessingDoneWithCustomClass;
According to Standard .NET event patterns you should create your own Args class.
Using an event model provides some design advantages. You can create multiple event listeners that perform different actions when a sought file [employee] is found. Combining the different listeners can create more robust algorithms.
… you should follow the convention and make it a reference (class) type. That means the argument object will be passed by reference, and any updates to the data will be viewed by all subscribers.
So always create your own event args
public class EmployeeFoundArgs : EventArgs
{
public Guid EmployeeId { get; }
public bool CancelRequested { get; set; }
public EmployeeFoundArgs(Guid id)
{
EmployeeId = id;
}
}
and use it like
public class Searcher : ISearch
{
// an event you can subscribe to when employee is found
public event EventHandler<EmployeeFoundArgs> EmployeeFound;
public void Search(string name)
{
Guid id;
//..some search code that finds the id..
// Fire the event
EmployeeFound?.Invoke(this,new EmployeeFoundArgs(id));
}
where you would subscribe to it and use it something like this
public class SearchViewModel
{
public SearchViewModel(ISearch search)
{
// Subscribe to the event
search.EmoloyeeFound += OnEmployeeFound;
}
private void OnEmployeeFound(object sender, EmployeeFoundArgs e)
{
Alert($"Found employee nr '{e.EmployeeId}');
}
}
Look for CancelRequested here to see how you could use that boolian value to cancel a search.
Syntactic sugar
Lets wrap this up with little syntactic sugar.
Following two lines
public delegate void ProcessingDoneEventHandler(object source, EventArgs args);
public event ProcessingDoneEventHandler ProcessingDone;
can be replaced with this one
public event EventHandler ProcessingDone;
Much less code and easier to understand. Right?
You can see they are the same if you mouse hover over the EventHandler delegate
Summary
There is a lot more to events and delegates. And hopefully I will cover that some day. But I hope you learned something at least reading this short post on events.
-
I don’t think they have a GraphQL endpoint but this was just to show that we could have a lot of code to send an alert that we wouldn’t want in the Factory class. ↩︎