C# Event Patterns
C# Patterns for Events are a little more complicated than you’ll find the VB.Net variant (And here’s the microsoft page that goes through it and here’s the index on the topic). C# makes use of Delegates to declare the signature used in an action and often requires another class to be declared which inherits from the EventArgs base class but we’ll get into that..
Delegates
Delegates are interesting objects which declare a signature and can then hold references to Methods with the same signature (or functions depending on the delegate declaration). Here’s a quick example of how one can be used:
public void UseDelegate() { // Declare the delegate delegate void MyDel(string str); // Assign a method to it MyDel Handler = DelegateMethod; // Use the method Handler("Hello World!"); } // Create a method for a delegate. public static void DelegateMethod(string message) { System.Console.WriteLine(message); }
Delegates can also be passed as parameters which enables callback functionality, I won’t go into that here but the Microsoft page will explain all of that and everything that I don’t cover here.
Built-in Delegates
There are a number of delegates built into the .Net framework so that you don’t have to create your own each time you want to create an event, for example the Action delegate provides a way to use generic delegate which does not return a value but can accept up to 16 parameters. For the purposes of events however we’ll be making use of the EventHandler delegate class.
EventHandler Delegates
Within .Net there are many conventions on how things should be done and events are not immune to this. Generally speaking it is good practice to return an instance of the calling object from an event to allow any subscribers to interrogate the object itself and pull any instance information needed, it is also the convention to return a class which inherits from the EventArgs baseclass. Whilst you won’t need to see it the EventHandler delegates are declared as:
public delegate void EventHandler(object sender, EventArgs e); public delegate void EventHandler(object sender, TEventArgs e);
The first declaration is of the EventHandler designed not to return any information and the second allows for a custom class that inherits from EventArgs to be specified at declaration.
Actually declaring actions then…
An action can be declared in the following way:
Public Event EventHandler EventOnly; Public Event EventHandler EventWithData;
where EventHandler is the delegate object and EventOnly is the name of the event. In the second declaration we can see that a class type of CustomEventArgs has been passed to the generic EventHandler declaration, this sets the EventHandler to expect a class of type CustomEventArgs as the second parameter when the events are being used, if we assume a CustomEventArgs class with the following declaration:
Public Class CustomEventArgs : EventArgs { Public string Message; }
Then the two events that we have declared can be used in the following ways, Here is an entire class declaration;
Public Class Sender { Public Event EventHandler EventOnly; Public Event EventHandler EventWithData; public void RaiseEvents() { EventOnly(this, EventArgs.Empty); CustomEventArgs Args = New CustomEventArgs(); Args.Message = "We've fired an action with some information!" EventWithData(This, Args); } }
Subscribing
We’ve shown how to declare and internally call an event but we’ve not looked at how to subscribe to an event, a very important part of the transaction!. Simply create a method that has the same signature as the action that you’d like to handle and literally add it to the action on the object you’d like to handle, if you’ve used the standard practice and inherited from the EventArgs baseclass then the signature is very simple. Below is an example of a class that subscribes to the events declared in our Sender class above:
Public Class Subscriber { Sender SendObj = new Sender(); Public Subscriber() { // Set the methods which are going to handle our actions SendObj.EventWithData += HandleMessage; SendObj.EventOnly += HandleEventOnly; SendObj.RaiseEvents(); } // Action handling methods public void HandleEventWithData(object o, EventArgs e) { MessageBox.Show(e.Message); } public void HandleEventOnly(object o, EventArgs e) { MessageBox.Show("EventOnly has been triggered!"); } }
In Practice
Now you’d be forgiven for thinking that we’ve now covered actions in their entirety but there is a little more to go over as you wouldn’t want to actually implement actions in this way. Firstly, if there are no subscribers to an action and it is called directly as above then you’ll get an error as the delegate associated doesn’t actually point to any methods so that will need to be insulated for a start. There is also an inheritance issue with public actions in that they are a special type of delegate which can only be called by their declaring class, that means that any inheriting classes will not be able to access it, it is also desirable to eliminate the possibility of any race conditions. The fix for all of these issues is to create a Protected Virtual method to raise each action, this method can then be called or overridden from any inheriting class. There is a pleasant upside to this as well, if you want to provide passthrough for actions (directly passing an action from a property or class variable to an action on the subscribing class which would pass the objects to any subscribers to that class) it’s much easier as you already have the methods to set as subscribers. So in its entirety below is a set of objects which conform to the pattern that you’d want to actually use for events in C#:
public class Sender { public event EventHandler EventOnly; public event EventHandler<CustomEventArgs> EventWithData; protected virtual void OnRaiseEventOnly(object o, EventArgs e) { // Take a copy of the event to avoid any race conditions EventHandler EVOnly = EventOnly; // Test for subscribers EVOnly?.Invoke(o, e); } protected virtual void OnRaiseEventWithData(object o, EventArgs e) { // Take a copy of the event to avoid any race conditions EventHandler<CustomEventArgs> EVWData = EventWithData; // Test for subscribers EVWData?.Invoke(o, e); } public void RaiseEvents() { OnRaiseEventOnly(this, EventArgs.Empty); OnRaiseEventWithData(this, New CustomEventArgs("We've fired an action with some information!")); } } public class CustomEventArgs : EventArgs { public string Message; public CustomEventArgs(){} public CustomEventArgs(string _Message) { Message = _Message; } } Public Class Subscriber { Sender SendObj = new Sender(); Public Subscriber() { // Set the methods which are going to handle our actions SendObj.EventWithData += HandleMessage; SendObj.EventOnly += HandleEventOnly; } // Action handling methods public void HandleEventWithData(object o, EventArgs e) { MessageBox.Show(e.Message); } public void HandleEventOnly(object o, EventArgs e) { MessageBox.Show("EventOnly has been triggered!"); } }