Delegates and Events In C# .NET
The concept of events and delegates is a little bit confusing for some of us. I was one of them when I started.
I hope by the end of this article you have a good and in-depth understanding of Events and Delegates, why we need them, how to use them, and how to create them.
?
The Event is a mechanism for communication between objects. This means when something happens inside an object, it can notify other objects about that. we can benefit from that while building loosely coupled applications.
What is a loosely coupled application? It's an application that which its components or classes are not tightly coupled together. loosely coupled application is easy to extend without breaking the code.
Ex: if sending an email to the owner of the video after the encoding process.
public class VideoEncoder
{
public void Encode(Video video)
{
//Encoding Logic
// ...
_mailService.Send(new Mail());
}
}
Nothing wrong with this code but in the future if I need to send a message to the owner of the video I have to edit the original code.
Of course, this is for simplicity but in real world scenario, it could be an extra line of code.
public class VideoEncoder
{
public void Encode(Video video)
{
//Encoding Logic
// ...
_mailService.Send(new Mail());
_messageService.Send(new Text());
}
}
The problem here is that we added these two extra lines when that Encode method changed which means it has to be recompiled so the VideoEncoder class also has to be recompiled which means any other classes that are dependent on VideoEncoder have to be recompiled and redeployed.
The problem here with this code by adding that extra line may break by accidentally something along the way.
As a software engineer, you want to reduce this problem by designing the application such that when you want to change the application that change has minimal impact on the overall application.
Now we can use events to solve this problem. We can make the VideoEncoder class publish (event sender) an event and make the MailService class subscribe (event receiver) to this event.
What is interesting about that is the VideoEncoder knows absolutely nothing about the MailService which means in the future if we want to extend our application and add the capability to send a message when the video is encoded simply create a new class called MessageService that has to subscribe to that event on the VideoEncoder.
So basically the VideoEncoder does not need to be recompiled and redeployed and simply extends the application by adding a new class so this reduces the impact on the application.
So let's take a look if we want to implement this events mechanism between classes:
First, we need to introduce a new concept called a delegate. The delegate is an agreement/contract between the publisher and the subscriber. It determines the signature of the event handler method in the subscriber. We will clarify by code example in seconds.
#Steps:
1) define a delegate.
2) Define an event based on the delegate.
3) Raise the event.
let's create the MediaEncoder class as a publisher:
namespace EventsAndDelegates
{
internal class MediaEncoder
{
//1) define a delegate
public delegate void AudioEncodedEventHandler(object source, EventArgs args);
//2) Define an event based on the delegate
public event AudioEncodedEventHandler? AudioEncoded;
public void Encode()
{
Console.WriteLine("Encoding video ...");
Thread.Sleep(3000);
//Assume we will notify all the subscribers
OnAudioEncoded();
}
//3) Raise the event
protected virtual void OnAudioEncoded()
{
//#If any subscribers raise the event
if (AudioEncoded != null)
AudioEncoded(this, EventArgs.Empty);
}
}
}
Notice that:
Naming Convention:
领英推荐
let's create two classes MailService and MessageService as subscribers:
namespace EventsAndDelegates
{
internal class MailService
{
public void OnAudioEncoded(object source, EventArgs args)
{
Console.WriteLine("MailService: sending an email...");
}
}
}
namespace EventsAndDelegates
{
internal class MessageService
{
public void OnAudioEncoded(object source, EventArgs args)
{
Console.WriteLine("MessageService: Sending a text message...");
}
}
}
Now we need to subscribe to the event (register a subscriber) as shown in the program.cs:
using EventsAndDelegates;
var mediaEncoder = new MediaEncoder(); //publisher
var mailService = new MailService(); //subscriber
var messageService = new MessageService(); //subscriber
mediaEncoder.AudioEncoded += mailService.OnAudioEncoded;
mediaEncoder.AudioEncoded += messageService.OnAudioEncoded;
mediaEncoder.Encode();
mailService.OnAudioEncoded references or points for that method OnAudioEncoded at MailService. So mediaEncoder.AudioEncoded event behind the seen is a list of pointers' methods.
Now to the next level if we want to send additional data when raising the event:
let's repeat the same example code with passing additional data.
we need to create a new class (VideoEventArgs) that is inherited from the EventArgs class and then pass the data on it:
namespace EventsAndDelegates;
internal class VideoEventArgs : EventArgs
{
public Video? Video { get; set; }
}
internal class Video
{
public string? Title { get; set; }
}
Now let's rewrite the code:
let's create the MediaEncoder class as a publisher:
namespace EventsAndDelegates
{
internal class MediaEncoder
{
//1) define a delegate
public delegate void VideoEncodedEventHandler(object source, VideoEventArgs args);
//2) Define an event based on the delegate
public event VideoEncodedEventHandler? VideoEncoded;
public void Encode(Video video)
{
Console.WriteLine("Encoding video ...");
Thread.Sleep(3000);
//Assume we will notify all the subscribers
OnVideoEncoded(video);
}
//3) Raise the event
protected virtual void OnVideoEncoded(Video video)
{
if (VideoEncoded != null)
VideoEncoded(this, new VideoEventArgs() {Video = video});
}
}
}
let's create two classes MailService and MessageService as subscribers:
namespace EventsAndDelegates
{
internal class MailService
{
public void OnVideoEncoded(object source, VideoEventArgs e)
{
Console.WriteLine("MailService: sending an email..." + e.Video?.Title);
}
}
}
namespace EventsAndDelegates
{
internal class MessageService
{
public void OnVideoEncoded(object source, VideoEventArgs e)
{
Console.WriteLine("MessageService: Sending a text message..." + e.Video?.Title);
}
}
}
Now we need to subscribe to the event (register a subscriber) as shown in the program.cs:
using EventsAndDelegates;
var video = new Video() { Title = "Video 1"};
var mediaEncoder = new MediaEncoder(); //publisher
var mailService = new MailService(); //subscriber
var messageService = new MessageService(); //subscriber
mediaEncoder.VideoEncoded += mailService.OnVideoEncoded;
mediaEncoder.VideoEncoded += messageService.OnVideoEncoded;
mediaEncoder.Encode(video);
Finally, I want to mention that there's a shorter way to create the delegate and event:
1) EventHandler: if we don't need to send any data with the event.
2) EventHandler<TEventArgs>: send additional data
Let's rewrite the MediaEncoder for handling the AudioEncoded event:
namespace EventsAndDelegates
{
internal class MediaEncoder
{
public event EventHandler? AudioEncoded;
public void Encode()
{
Console.WriteLine("Encoding video ...");
Thread.Sleep(3000);
OnAudioEncoded();
}
protected virtual void OnAudioEncoded()
{
if (AudioEncoded != null)
AudioEncoded(this, EventArgs.Empty);
}
}
}
Let's rewrite the MediaEncoder for handling the VideoEncoded event:
namespace EventsAndDelegates
{
internal class MediaEncoder
{
public event EventHandler<VideoEventArgs>? VideoEncoded;
public void Encode(Video video)
{
Console.WriteLine("Encoding video ...");
Thread.Sleep(3000);
OnVideoEncoded(video);
}
protected virtual void OnVideoEncoded(Video video)
{
if (VideoEncoded != null)
VideoEncoded(this, new VideoEventArgs() {Video = video});
}
}
}