Skip to Content
Course content

45: Delegates Explained

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've looked at the C# documentation for delegates, you've probably seen them described as "type-safe function pointers." That's technically true, but it's a textbook definition that doesn't actually tell you why you'd use one. In plain English: a delegate is a way to treat a method like a variable. You can pass a method into another method, store it in a list, or swap it out at runtime.

To make this click, let's build a simple notification system. Imagine we're writing a `UserAccount` service. When a password is changed, we need to notify the user. But here's the catch: some users want an email, some want an SMS, and some might want a push notification. I don't want my `AccountService` to have a giant if/else block checking user preferences every time a password changes.

The problem with hardcoded logic

Initially, I might have written something like this. It's simple, but it's rigid:

public class AccountService 
{
    public void ChangePassword(string newPassword) 
    {
        // ... logic to update password in DB ...
        Console.WriteLine("Password updated in database.");
        
        // Hardcoded notification
        SendEmailNotification("Your password was changed!");
    }

    private void SendEmailNotification(string message) 
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

The problem is that ChangePassword is now married to SendEmailNotification. If I want to add SMS support, I have to modify the AccountService class. That violates the Open/Closed Principle—we should be able to add new notification methods without touching the core business logic.

Defining our function contract

This is where the delegate comes in. Think of a delegate as a "contract" for a method. I'm telling C#, "I don't care which method is called, as long as it returns void and takes a single string as an argument."

// This defines the "shape" of the method we want to pass around
public delegate void NotificationHandler(string message);

public class AccountService 
{
    public void ChangePassword(string newPassword, NotificationHandler notifyMethod) 
    {
        // ... logic to update password ...
        Console.WriteLine("Password updated in database.");

        // We call the delegate here, not a specific method
        notifyMethod("Your password was changed!");
    }
}

Now, the AccountService doesn't know how the notification happens; it just knows it has a NotificationHandler it can trigger.

The signature mismatch headache

Here is where I usually trip up when I'm first prototyping these. I tried to add a logging method to my system to track when notifications were sent, but I forgot that delegates are incredibly strict about signatures.

public class NotificationUtils 
{
    public static void SendSms(string msg) => Console.WriteLine($"SMS: {msg}");
    public static void LogToDisk(string msg, DateTime timestamp) => Console.WriteLine($"Logged {msg} at {timestamp}");
}

// ... inside Main ...
AccountService service = new AccountService();

// This works fine
service.ChangePassword("Secret123", NotificationUtils.SendSms);

// I tried this, and the compiler screamed at me:
service.ChangePassword("Secret123", NotificationUtils.LogToDisk); 

The error is essentially saying: "Cannot convert method 'LogToDisk' to delegate 'NotificationHandler'." Why? Because LogToDisk requires two arguments (string and DateTime), but our NotificationHandler delegate only allows one. A delegate isn't just a pointer; it's a type. If the signatures don't match exactly, it won't compile. To fix this, I had to wrap the logging call in a method that matched the delegate's signature or use a lambda expression.

Stacking notifications with multicast

One of the coolest things about delegates is that they can be "multicast." This means a single delegate variable can actually hold a list of multiple methods and call them all in sequence.

NotificationHandler notifications = NotificationUtils.SendSms;
notifications += NotificationUtils.SendEmail; // Imagine this method exists

// Now, when I pass 'notifications' to the service, 
// BOTH the SMS and Email methods will be executed.
service.ChangePassword("NewPass456", notifications);

By using the += operator, I've created a chain of events. The AccountService still thinks it's just calling one method, but C# is actually iterating through the internal list of methods attached to that delegate. It's a lightweight way to implement the Observer pattern without building a complex event system from scratch.




📋 Practical Task

Exercise: Building a Custom Data Filter Pipeline

You are building a data processing tool that takes a list of strings (raw logs) and applies various filters to them before printing them. Instead of hardcoding the filters, you will use delegates to make the pipeline customizable.

Your Requirements:

  • Define a delegate named LogFilter that takes a string and returns a bool (true if the log should be kept, false if it should be discarded).
  • Create a class LogProcessor with a method ProcessLogs(List<string> logs, LogFilter filter). This method should iterate through the list and only print the strings that the filter delegate returns true for.
  • Write two separate static methods: IsError (returns true if the string contains the word "ERROR") and IsWarning (returns true if the string contains "WARN").
  • In your Main method, call ProcessLogs twice: once passing the IsError method and once passing the IsWarning method.
  • Bonus: Try using a lambda expression as the filter to only keep logs that are longer than 10 characters.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.