Course Content
Advanced C# with .NET
Advanced C# with .NET
Custom Events
In the previous chapter, we learnt how to bind methods to event handlers, however, is it possible to create our own Events? Yes, in fact it is also quite simple to do so.
An event system consists of two parts: The Publisher class and the Subscriber class. There is always a single Publisher class for an event, however there can be multiple Subscriber classes.
A Publisher class contains the event handler delegate for the event, and a method to invoke that delegate whenever relevant.
A class is referred to as a Subscriber class in this context if it subscribes any method to the event handler of the Publisher class.
The following diagram shows the process flow of an Event System:
We will create a simple event system starting from the default project. Inside the C# file, we first create a new class called CounterEvent
which is going to represent the Publisher class, and it's going to have an EventHandler instance which we create using the already existing EventHandler
type:
index
public class CounterEvent { public event EventHandler AtTensMultiple; }
This class also needs a method to invoke the event handler whenever necessary. We can create an event which is triggered whenever the condition count % 10 == 0
is satisfied, where count
is the number of times the user has clicked the CounterBtn
. Let's first define the invoke method inside the CounterEvent, we will call is CountCheck
because it will basically check the value of count
every time it's called and it will invoke AtTensMultiple
when the condition is satisfied.
index
public class CounterEvent { public event EventHandler? AtTensMultiple; public void CountCheck(int count) { if(count % 10 == 0) { EventHandler? handler = AtTensMultiple; if(handler) { AtTensMultiple(this, new EventArgs()); } } } }
Here this
argument is being passed to represent the sender
and new EventArgs()
represents a new instance of an empty class. We can pass data into the methods that have subscribed to the event handler, when the event handler is invoked, using this second argument, however since we are passing EventArgs
which is an empty class, no data is passed. We will see how to pass data this way in a bit.
Now that the invoke method is defined, our publisher class is pretty much complete. We need to do two more things:
- Create an instance of the publisher class;
- Call the invoke method to make it functional;
So, first I will create an instance inside the MainPage class:
index
public static CounterEvent counter = new CounterEvent();
And now, let's call this method inside OnCounterClicked
so that it runs a check every time the user clicks the button.
index
private void OnCounterClicked(object? sender, new EventArgs e) { count++; if(count == 0) CounterBtn.Text = $"Clicked {count} time"; else CounterBtn.Text = $"Clicked {count} times"; counter.CounterCheck(count); }
Now that our event is functional, we are ready to bind a method to the event handler. I will create a new method called Reformat
which simply appends (Ten's Multiple)
text to the end of the button text whenever the method is invoked. Our complete code will look something like this by now:
index
using System.Runtime.CompilerServices; namespace EventsIntro { public class CounterEvent { public event EventHandler? AtTensMultiple; public void CountCheck(int count) { EventHandler? handler = AtTensMultiple; if (handler != null) { if (count % 10 == 0) { handler(this, new EventArgs()); } } } } public partial class MainPage : ContentPage { int count = 0; CounterEvent counter = new CounterEvent(); public MainPage() { InitializeComponent(); CounterBtn.Clicked += OnCounterClicked; counter.AtTensMultiple += Reformat; } private void Reformat(object? sender, EventArgs e) { CounterBtn.Text += " " + "(Ten's Multiple)"; } private void OnCounterClicked(object? sender, EventArgs e) { count++; if (count == 1) CounterBtn.Text = $"Clicked {count} time"; else CounterBtn.Text = $"Clicked {count} times"; counter.CountCheck(count); } } }
So now if we run the program and click the button 10 times, the text (Ten's Multiple)
will appear in the button text. If you press it 20 times, the same will happen again. And so on.
Now, to pass arguments into the Reformat
method, we can create a new class called CustomEventArgs
derived from EventArgs
, and can have some public properties in that class which can represent the parameters:
index
public class CounterArgs : EventArgs { public string Text { get; } // Here 'Text' can act as a parameter or argument. public CounterArgs(string text) { this.Text = text; } }
Now that we have a CustomEvenArgs
class, we need make a few changes to the CounterEvent
class:
index
public class CounterEvent { public delegate void CounterEventHandler(object sender, CounterArgs e); public event CounterEventHandler? AtTensMultiple; public void CountCheck(int count) { CounterEventHandler? handler = AtTensMultiple; if (handler != null) { if (count % 10 == 0) { handler(this, new CounterArgs("(something random)")); } } } }
In the code above, we first create a new definition for the EventHandler and call it CustomEventHandler
, where we set the datatype of the second parameter to CustomEventArgs
.
Secondly, we pass new CounterArgs("something random")
as a second argument when invoking the event handler. This lets us mass the string data (something random)
as an argument to the Refactor
method, which we can access like this:
index
// Modified signature to support 'CounterArgs' instead of 'EventArgs' private void Reformat(object? sender, CounterArgs e) { CounterBtn.Text += " " + e.Text; }
So, now, our final code should look something like this:
index
using System.Runtime.CompilerServices; namespace EventsIntro { public class CounterEvent { public delegate void CustomEventHandler(object? sender, CounterArgs e); public event CustomEventHandler? AtTensMultiple; public void CountCheck(int count) { CustomEventHandler? handler = AtTensMultiple; if (handler != null) { if (count % 10 == 0) { handler(this, new CounterArgs("(something random)")); } } } } public class CounterArgs : EventArgs { public string Text { get; } public CounterArgs(string text) { this.Text = text; } } public partial class MainPage : ContentPage { int count = 0; CounterEvent counter = new CounterEvent(); public MainPage() { InitializeComponent(); CounterBtn.Clicked += OnCounterClicked; counter.AtTensMultiple += Reformat; } private void Reformat(object? sender, CounterArgs e) { CounterBtn.Text += " " + e.Text; } private void OnCounterClicked(object? sender, EventArgs e) { count++; if (count == 1) CounterBtn.Text = $"Clicked {count} time"; else CounterBtn.Text = $"Clicked {count} times"; counter.CountCheck(count); } } }
If you run the above program, the text (something random)
will be appended to the end of the button text whenever the count
is a multiple of ten. Which indicates that the data is successfully being passed into the subscriber methods.
Important Points:
- The Publisher Class contains the definition and an instance of the custom event handler delegate;
- The can skip the definition of the Event Handler Delegate if we are using
EventHandler
delegate type to create the instance; - There is a method in the Publisher Class which is responsible for invoking the delegate whenever it's relevant;
- The Delegate Instance should be public to make it accessible for subscriber classes;
- We can pass arguments into Event Handler Methods by creating our own class which derives from
EventArgs
and having properties that represent the data or arguments which we want to pass;
Thanks for your feedback!