Drani Academy – Interview Question, Search Job, Tuitorials, Cheat Sheet, Project, eBook

C#.Net

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:

  1. Define a delegate that represents the event’s signature.
  2. Declare an event using the event keyword and the delegate type.
  3. 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:

  1. 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 and EventArgs e.

  2. 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:

  1. add: This accessor is used to subscribe to the event. It defines what should happen when a new subscriber registers for the event.

  2. 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:

  1. Try-Catch Inside Event Handler: Wrap the event handler code in a try-catch block to catch exceptions and handle them gracefully.

  2. Aggregating Exceptions: If multiple subscribers can throw exceptions, consider aggregating these exceptions in the event publisher and handling them there.

  3. 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.