Event Creation in .NET Core
Events are a fundamental mechanism in .NET Core for enabling communication between objects. They allow an object (the publisher) to notify other objects (the subscribers) when a particular action or occurrence happens. This pattern is crucial for building responsive and decoupled applications.
Understanding Delegates
At the heart of the .NET event system are delegates. A delegate is a type that represents references to methods with a particular parameter list and return type. Think of it as a strongly typed function pointer.
To create an event, you first need a delegate type that defines the signature of the methods that will handle the event.
Defining the Delegate
The convention for event-handling delegates is to have a `void` return type and accept two parameters:
- The first parameter is an object representing the instance that raised the event (the publisher).
- The second parameter is of a type derived from EventArgs, which carries event-specific data.
Example: Defining an Event Data Class and Delegate
Let's define a custom class to hold event data:
public class ProgressChangedEventArgs : EventArgs
{
public int PercentageComplete { get; }
public ProgressChangedEventArgs(int percentage)
{
PercentageComplete = percentage;
}
}
Now, define the delegate type:
public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);
Alternatively, and more commonly, you can use the built-in generic delegate EventHandler<TEventArgs>:
// Using the generic delegate
public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);
// Is equivalent to:
// public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
Declaring the Event
Within the publisher class, you declare the event using the event keyword, followed by the delegate type and the event name.
Example: Publisher Class with an Event
public class DataUploader
{
// Declare the event using the custom delegate
public event ProgressChangedEventHandler ProgressChanged;
// Or using the generic EventHandler
// public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
public void UploadData(string data)
{
Console.WriteLine("Starting data upload...");
for (int i = 0; i < 100; i++)
{
// Simulate upload progress
System.Threading.Thread.Sleep(50);
OnProgressChanged(i); // Raise the event
}
Console.WriteLine("Data upload complete.");
}
protected virtual void OnProgressChanged(int percentage)
{
// Create the event arguments
ProgressChangedEventArgs args = new ProgressChangedEventArgs(percentage);
// Safely invoke the event
// The '?' handles the case where there are no subscribers
ProgressChanged?.Invoke(this, args);
}
}
The OnProgressChanged method is a common convention for raising events. It's often made protected virtual to allow derived classes to override the event-raising behavior.
Raising the Event
The publisher raises an event by invoking the delegate associated with it. The ?.Invoke() syntax is a safe way to do this, as it checks if there are any subscribers before attempting to invoke the delegate. If no methods are subscribed to the event, Invoke() will not be called, preventing a NullReferenceException.
Subscribing to an Event
Subscriber objects can subscribe to events by providing a method that matches the delegate's signature. This is done using the += operator.
Example: Subscriber Class
public class ConsoleProgressBar
{
public void Subscribe(DataUploader uploader)
{
// Subscribe to the ProgressChanged event
uploader.ProgressChanged += HandleProgressChanged;
}
public void Unsubscribe(DataUploader uploader)
{
// Unsubscribe from the ProgressChanged event
uploader.ProgressChanged -= HandleProgressChanged;
}
private void HandleProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine($"Upload Progress: {e.PercentageComplete}%");
// You could update a UI progress bar here
}
}
Unsubscribing from an Event
It's crucial to unsubscribe from events when they are no longer needed to prevent memory leaks. This is done using the -= operator. Failure to unsubscribe can lead to a subscriber object being kept in memory longer than intended, even if it's no longer actively used.
Putting It All Together
Here's how you might use these classes:
Example: Main Program Flow
public class Program
{
public static void Main(string[] args)
{
DataUploader uploader = new DataUploader();
ConsoleProgressBar progressBar = new ConsoleProgressBar();
// Subscribe the progress bar to the uploader's event
progressBar.Subscribe(uploader);
// Start the data upload, which will trigger events
uploader.UploadData("MyImportantData");
// Optionally unsubscribe later
// progressBar.Unsubscribe(uploader);
Console.ReadKey(); // Keep console open
}
}
Best Practices for Event Handling
- Use the
eventkeyword for declaring events. - Define event data using classes derived from
EventArgs. - Use the
EventHandler<TEventArgs>generic delegate when possible. - Make event-raising methods
protected virtual. - Always use the null-conditional operator
?.Invoke()when raising events. - Ensure subscribers unsubscribe from events to prevent memory leaks.
- Keep event handlers focused and perform minimal work to avoid blocking the publisher.