Introduction
Learning language keywords doesn’t mean that you’ll be able to use them. It is really hard to find real practical tutorials when it comes to learning things in practice. You can find tons of tutorials that explain delegates but 99% of them are just nonsense. For me, if you can’t explain it in practice with real-world examples, it means you also don’t know it well. Having Method1(), Method2(), foo, Bar, and Rabbit-type examples is not practical. A man, who reads or watches your tutorial wants:
- Understand the topic
- Understand when and why to use it
Most of the tutorials just bring to the table the first one.
In this series of tutorials, my point is, to not just explain, but also to help you to understand why and when to use it.
Let’s get started
We, as Developers, use delegates on an almost daily basis. It is a cool tool to wrap your method and send it as an argument to another method. The logic here is: Method accepts another method and using it you can add flexibility, maintainability, and extensibility to your methods.
PS: Download the source code from the github to follow us. ( Project names are: EAP and EAP_Console)
What is an Event-Based Asynchronous pattern?
In C#, an Event-Based Asynchronous pattern ( in short EAP) is a well-established pattern for designing asynchronous operations and exposing them to client code. It leverages the familiar events and delegates model to allow clients to initiate asynchronous operations, register for event notifications, and handle results or errors when they become available.
It was introduced in .NET 2.0 and it is a cool design pattern if you want:
- To support older .NET versions that don’t have TPL
- If you prefer the events and delegates model for asynchronous programming
- If you want to have fine-grained control over event notifications and state management
Advantages of EAP
Consider the following advantages of EAP.
- Familiar events and delegates model for event handling.
- Flexibility in designing event types and delegate signatures.
- Useful for integrating with legacy code that might not support newer asynchronous patterns.
Key characteristics
It is always easy to detect the pattern in C# code using the following key characteristics:
- Method Naming: Asynchronous methods typically end with "Async" to signal their non-blocking nature.
- Events: Each asynchronous operation exposes at least two events:
- Completed: Raised when the operation finishes successfully or encounters an error.
- ProgressChanged (optional): Provides progress updates during long-running operations.
- Delegates: Event handlers are registered using delegates, enabling clients to specify code to execute when events are raised.
- State Management: Clients often provide custom state objects to BeginXXX methods to store context and pass information between the asynchronous operation and its completion handler.
Well, the point here is, that we use delegates when implementing this pattern.
Let’s implement it.
If you want to follow the example, just download the source code from github.
- Open Visual Studio. Select WindowsForms->(I’ll use .NET 8) ->rename the form to MainForm.cs
- Add progressbaritem( name it to progressBarItem) and button (name it to btn_download)
- Add backgroundworker component (rename it to bgworker).
Background worker has several methods to work in the background.
One of them is the DoWork method. Let's load some XML asynchronously without blocking our UI thread.
private XmlDocument document = null!;
private void btn_download_Click(object sender, EventArgs e)
{
this.bgWorker.RunWorkerAsync();
this.btn_download.Enabled = false;
while (this.bgWorker.IsBusy)
{
progressBarItem.Increment(1);
//the form remains responsive during the asynchronous operation.
Application.DoEvents();
}
}
private void bgWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
document = new XmlDocument();
document.Load(@"http://restapi.adequateshop.com/api/Traveler?page=1");
}
As you understand, DoWork is the main method that does the operation. On the other hand, RunWorkerCompleted notifies us at the end when the operation is done. Both methods are going to be part of our events.
private void bgWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
progressBarItem.Value = 100;
if (e.Error == null)
{
MessageBox.Show(document.InnerXml, "Download Complete");
}
else
{
MessageBox.Show(
"Failed to download file",
"Download failed",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
// Enable the download button and reset the progress bar.
this.btn_download.Enabled = true;
progressBarItem.Value = 0;
}
We build events based on delegates.
private void InitializeComponent()
{
bgWorker = new System.ComponentModel.BackgroundWorker();
progressBarItem = new ProgressBar();
btn_download = new Button();
SuspendLayout();
//
// bgWorker
// Events work based on Delegates
bgWorker.DoWork += bgWorker_DoWork;
bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
As you might understand, EAP is not just for WindowsForms. It is an independent design pattern implementation that helps you to use it on any project you want. Let’s proof it and use it in our Console Application.
- Go to Visual Studio
- Select Console Application
- rename it to EAP_Console
You can just download the source code from github and run/investigate/learn it without following me.
Let's download some file from the internet and store them in our C: drive. (check our EAP_Console app).
static void Main(string[] args)
{
WebClient client = new();
client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCallback!);
client.DownloadFileAsync(new Uri("https://filesamples.com/samples/document/txt/sample3.txt"), @"C:\test\file.txt");
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
static void DownloadFileCallback(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
Console.WriteLine("Download failed: {0}", e.Error.Message);
}
else
{
Console.WriteLine("Download completed successfully!");
}
}
DownloadFileCallBack is our delegate that we use for AsyncCompletedEventHandler. It helps us to be notified when the operation is done!
Conclusion
Delegates are a very powerful tool that allows you to wrap your method into the box and send it to a method as an argument. Properly applying it will help you have extensible and reusable code.