C#.Net
- Chapter 1: Introduction to C# and .NET
- Chapter 2: C# Basics
- Chapter 3: Control Flow
- Chapter 4: Methods and Functions
- Chapter 5: Object-Oriented Programming (OOP)
- Chapter 6: Collections and Generics
- Chapter 7: Exception Handling
- Chapter 8: File I/O and Serialization
- Chapter 9: Delegates and Events
- Chapter 10: Asynchronous Programming
- Chapter 11: Working with Databases (ADO.NET)
- Chapter 12: Windows Forms and GUI Programming
- Chapter 13: Web Development with ASP.NET
- Chapter 14: Web Services and API Development
- Chapter 15: Unit Testing and Test-Driven Development (TDD)
- Chapter 16: Advanced Topics (Optional)
- Chapter 17: Best Practices and Design Patterns
- Chapter 18: Deployment and Hosting
- Chapter 19: Security in C#/.NET
- Chapter 20: Project Development and Real-World Applications
Tutorials – C#.Net
Chapter 9: Delegates and Events
Chapter 9 of our C# tutorial explores the important concepts of delegates and events. Delegates are a fundamental feature of C# that allow you to work with methods as first-class citizens. Events, built on top of delegates, enable you to create and manage the publish-subscribe model for handling and responding to various types of notifications and changes in your application. In this chapter, we’ll dive deep into the world of delegates and events in C#.
9.1 Introduction to Delegates
A delegate in C# is a type that represents references to methods with a specific signature. Delegates provide a way to work with methods as objects, making them first-class citizens in the language. Delegates are similar to function pointers in other programming languages.
Delegates are particularly useful in scenarios where you want to pass methods as parameters, create callback mechanisms, or implement event handling. Delegates can be thought of as a function pointer that points to one or more methods.
9.2 Creating and Using Delegates
To create a delegate, you need to define its signature, which specifies the return type and the parameters of the methods it can reference. Here’s a simple example:
// Delegate definition
delegate int MathOperation(int a, int b);
class Program {
static int Add(int a, int b) {
return a + b; }
static int Subtract(int a, int b) {
return a - b; }
static void Main() {
// Create delegate instances MathOperation addDelegate = Add; MathOperation subtractDelegate = Subtract;
// Use the delegates to call methods
int result1 = addDelegate(5, 3); // Calls Add(5, 3)
int result2 = subtractDelegate(10, 4); // Calls Subtract(10, 4)
Console.WriteLine($"Result1: {result1}, Result2: {result2}");
}
}
In this example, we define a MathOperation
delegate with a signature that takes two integers as parameters and returns an integer. We create instances of this delegate and assign methods (Add
and Subtract
) to them. Using the delegates, we can call these methods as if they were regular functions.
9.3 Multicast Delegates
C# delegates can reference multiple methods, which allows you to invoke multiple methods with a single delegate call. Delegates that can reference multiple methods are called multicast delegates.
Here’s an example of a multicast delegate:
delegate void MultiOperation(int a);
class Program
{
static void PrintNumber(int number)
{
Console.WriteLine($"Number: {number}");
}
static void DoubleNumber(int number)
{
Console.WriteLine($"Double: {number * 2}");
}
static void Main()
{
MultiOperation multiDelegate = PrintNumber;
multiDelegate += DoubleNumber; // Adding another method
multiDelegate(5);
}
}
In this code, the MultiOperation
delegate can reference both the PrintNumber
and DoubleNumber
methods. When we invoke the delegate, both methods are called.
9.4 Built-in Delegates
C# provides several built-in delegate types that can be used without defining custom delegate types. Some of the commonly used built-in delegate types include:
Action
: A delegate that takes zero or more parameters and returns void.Func
: A delegate that takes zero or more parameters and returns a value of a specified type.Predicate
: A delegate that represents a method that takes one parameter of a specified type and returns a Boolean value.EventHandler
: A delegate used in event handling scenarios.
Here’s an example of using Action
and Func
delegates:
class Program
{
static void Main()
{
// Action delegate with no parameters and no return value
Action greet = () => Console.WriteLine("Hello, World!");
greet();
// Func delegate with two integer parameters and an integer return value
Func<int, int, int> add = (a, b) => a + b;
int sum = add(3, 7);
Console.WriteLine($"Sum: {sum}");
}
}
In this example, we use the Action
delegate to create a simple greeting method, and the Func
delegate to perform addition.
9.5 Events in C#
Events are a powerful mechanism in C# that allow one class to notify other classes or objects when something of interest happens. They are often used to implement the publish-subscribe model, where one class publishes events and other classes subscribe to them to respond to those events. Events are built on top of delegates and provide a safe and encapsulated way to register and notify subscribers.
To define an event, you typically follow these steps:
- Define a delegate that represents the event’s signature.
- Declare an event using the
event
keyword and the delegate type. - Raise the event when the event’s condition is met.
Here’s an example of defining and using events:
using System;
// Step 1: Define a delegate
delegate void MyEventHandler(string message);
class Publisher
{
// Step 2: Declare an event of the delegate type
public event MyEventHandler MessagePublished;
public void PublishMessage(string message)
{
Console.WriteLine($"Publishing message: {message}");
// Step 3: Raise the event
MessagePublished?.Invoke(message);
}
}
class Subscriber
{
public void OnMessageReceived(string message)
{
Console.WriteLine($"Received message: {message}");
}
}
class Program
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
// Subscribe to the event
publisher.MessagePublished += subscriber.OnMessageReceived;
publisher.PublishMessage("Hello, Subscribers!");
}
}
In this example, the Publisher
class declares an event named MessagePublished
, and the Subscriber
class subscribes to this event. When the Publisher
publishes a message, it raises the MessagePublished
event, and the subscribed Subscriber
responds to the event by invoking the OnMessageReceived
method.
9.6 Event Handlers and Delegates
Events use delegates as event handlers. An event handler is a method that is called in response to an event. In C#, the delegate that represents the event’s signature is used as the event handler delegate. This delegate should have a specific signature, which matches the event’s delegate type.
Here’s an example of an event handler:
class Publisher
{
public event EventHandler MessagePublished;
public void PublishMessage(string message)
{
Console.WriteLine($"Publishing message: {message}");
MessagePublished?.Invoke(this, new MessageEventArgs(message));
}
}
class MessageEventArgs : EventArgs
{
public string Message { get; }
public MessageEventArgs(string message)
{
Message = message;
}
}
class Subscriber
{
public void OnMessageReceived(object sender, EventArgs e)
{
if (e is MessageEventArgs messageArgs)
{
Console.WriteLine($"Received message: {messageArgs.Message}");
}
}
}
class Program
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
publisher.MessagePublished += subscriber.OnMessageReceived;
publisher.PublishMessage("Hello, Subscribers!");
}
}
In this example, we define an EventHandler
delegate as the event handler delegate for the MessagePublished
event. The Subscriber
class’s OnMessageReceived
method matches the signature of the EventHandler
. When the event is raised, it passes an instance of the MessageEventArgs
class, which holds the message to be delivered.
9.7 Event Patterns
C# provides two common event patterns:
Standard Event Pattern: This is the most common event pattern, and it’s used when you want to raise events with no custom event data. It uses the
EventHandler
delegate and typically has two parameters:object sender
andEventArgs e
.Custom Event Pattern: This pattern is used when you need to pass custom event data with the event. It uses a delegate that takes custom event data as a parameter. You define a class derived from
EventArgs
to carry the custom event data.
Here’s an example of using the standard and custom event patterns:
using System;
class Publisher
{
// Standard Event Pattern
public event EventHandler StandardEvent;
// Custom Event Pattern
public event CustomEventHandler CustomEvent;
public void RaiseStandardEvent()
{
StandardEvent?.Invoke(this, EventArgs.Empty);
}
public void RaiseCustomEvent(string data)
{
CustomEvent?.Invoke(this, new CustomEventArgs(data));
}
}
// Custom Event Pattern
delegate void CustomEventHandler(object sender, CustomEventArgs e);
class CustomEventArgs : EventArgs
{
public string EventData { get; }
public CustomEventArgs(string data)
{
EventData = data;
}
}
class Subscriber
{
public void OnStandardEvent(object sender, EventArgs e)
{
Console.WriteLine("Standard Event Received");
}
public void OnCustomEvent(object sender, CustomEventArgs e)
{
Console.WriteLine($"Custom Event Received: {e.EventData}");
}
}
class Program
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
// Subscribe to standard event
publisher.StandardEvent += subscriber.OnStandardEvent;
// Subscribe to custom event
publisher.CustomEvent += subscriber.OnCustomEvent;
publisher.RaiseStandardEvent();
publisher.RaiseCustomEvent("Custom Event Data");
}
}
In this example, the Publisher
class defines both a standard event and a custom event, along with methods to raise each event. The Subscriber
class subscribes to both events and responds to them using event handlers.
9.8 Event Accessors
Events in C# use accessors, similar to properties. These accessors allow you to customize how event subscribers are added and removed. The two primary accessors are:
add
: This accessor is used to subscribe to the event. It defines what should happen when a new subscriber registers for the event.remove
: This accessor is used to unsubscribe from the event. It defines how to remove a subscriber.
Here’s an example of customizing event accessors:
using System;
class Publisher
{
private event EventHandler _customEvent;
public event EventHandler CustomEvent
{
add
{
Console.WriteLine("Subscriber Added");
_customEvent += value;
}
remove
{
Console.WriteLine("Subscriber Removed");
_customEvent -= value;
}
}
public void RaiseCustomEvent()
{
_customEvent?.Invoke(this, EventArgs.Empty);
}
}
class Subscriber
{
public void OnCustomEvent(object sender, EventArgs e)
{
Console.WriteLine("Custom Event Received");
}
}
class Program
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
// Subscribe to custom event
publisher.CustomEvent += subscriber.OnCustomEvent;
publisher.RaiseCustomEvent();
// Unsubscribe from custom event
publisher.CustomEvent -= subscriber.OnCustomEvent;
publisher.RaiseCustomEvent();
}
}
In this example, the Publisher
class customizes the add
and remove
accessors for the CustomEvent
event. It prints messages when subscribers are added or removed.
9.9 Handling Exceptions in Event Subscribers
When subscribers of an event throw exceptions, it can disrupt the event invocation chain. To handle exceptions in event subscribers, you can follow these strategies:
Try-Catch Inside Event Handler: Wrap the event handler code in a
try-catch
block to catch exceptions and handle them gracefully.Aggregating Exceptions: If multiple subscribers can throw exceptions, consider aggregating these exceptions in the event publisher and handling them there.
Unsubscribing Faulty Subscribers: In some cases, you may want to automatically unsubscribe subscribers that repeatedly throw exceptions.
using System;
class Publisher
{
public event EventHandler CustomEvent;
public void RaiseCustomEvent()
{
foreach (Delegate subscriber in CustomEvent.GetInvocationList())
{
try
{
CustomEvent -= (EventHandler)subscriber;
subscriber.DynamicInvoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
Console.WriteLine($"Subscriber threw an exception: {ex.Message}");
}
}
}
}
class Subscriber
{
public void OnCustomEvent(object sender, EventArgs e)
{
Console.WriteLine("Custom Event Received");
throw new Exception("Subscriber Exception");
}
}
class Program
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
publisher.CustomEvent += subscriber.OnCustomEvent;
publisher.RaiseCustomEvent();
}
}
In this example, the RaiseCustomEvent
method iterates through the subscribers of the CustomEvent
event, calling them one by one. If a subscriber throws an exception, it is caught, and a message is printed. The publisher also automatically unsubscribes the faulty subscriber.
9.10 Conclusion of Chapter 9
In Chapter 9, you’ve learned about delegates and events in C#. Delegates are a powerful mechanism for working with methods as objects, allowing you to create dynamic method calls and callback mechanisms. Events, built on top of delegates, provide a structured way to implement the publish-subscribe pattern for handling notifications and changes in your applications.
Understanding delegates and events is crucial for building responsive and loosely coupled applications. By using events, you can design systems that can easily adapt to changes and extend functionality through event subscribers. Delegates and events are fundamental components of C# that will enhance your ability to create robust, extensible, and maintainable software.