# Mastering Delegates in C#

Delegates in C# are much more than just function pointers, they are foundational to many of the language’s most powerful features, including events, asynchronous programming, and LINQ. Understanding how to use delegates properly opens the door to writing flexible, decoupled, and testable code.

**What 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 contract that any matching method can satisfy.

```csharp
delegate int MathOperation(int a, int b);
```

This defines a delegate that can point to any method that takes two `int` parameters and returns an `int`.

Example usage:

```csharp
Add(int x, int y) => x + y;
MathOperation op = Add;
Console.WriteLine(op(3, 4)); // 7
```

## **Why Use Delegates?**

* Decoupling logic
    
* Flexibility in passing methods as parameters
    
* Enabling extensibility (e.g., callbacks, strategies)
    
* Core to events and LINQ
    
* Better unit testing with mockable logic. Multicast Delegates
    

Delegates can point to multiple methods using the `+=` and `-=` operators.

```csharp
notify = () => Console.WriteLine("First");
notify += () => Console.WriteLine("Second");
notify();
```

Return values from multicast delegates are only from the last invocation.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753438698234/31eb8768-82fd-4c75-9799-3d7f84b9e02a.png align="center")

## **Built In Generic Delegates: Func, Action, Predicate**

### Action

No return value:

```csharp
Action<string> log = message => Console.WriteLine($"Log: {message}");
log("Test");
```

### Func

Returns a value:

```csharp
Func<int, int, int> add = (a, b) => a + b;
int result = add(2, 3); // 5
```

### Predicate

Returns `bool`:

```csharp
Predicate<string> isEmpty = s => string.IsNullOrEmpty(s);
bool check = isEmpty("");
```

These are often more concise than custom delegate types.

## **Delegates as Strategy Pattern**

Instead of tightly coupling logic:

```csharp
public class PaymentProcessor
{
    public void Process(Payment payment) { /* logic */ }
}
```

Use delegates for pluggability:

```csharp
public class PaymentProcessor
{
    private readonly Action<Payment> _strategy;
    
    public PaymentProcessor(Action<Payment> strategy)
    {
        _strategy = strategy;
    }

    public void Process(Payment payment)
    {
        _strategy(payment);
    }
}
```

This makes the behaviour easily swappable, mockable, and testable.

## **Higher Order Functions and Currying**

A **higher order function** takes a delegate or returns one.

```csharp
Func<int, Func<int, int>> add = x => y => x + y;
var addFive = add(5);
Console.WriteLine(addFive(10)); // 15
```

Currying like this is rarely used in C# day to day but can unlock powerful abstractions in complex business rules or pipelines.

## **Delegates and Events**

Events are essentially multicast delegates with restrictions.

```csharp
public class Alarm
{
    public event Action? OnRing;

    public void Ring()
    {
        OnRing?.Invoke();
    }
}
```

Events let subscribers register methods:

```csharp
var alarm = new Alarm();
alarm.OnRing += () => Console.WriteLine("Wake up!");
alarm.Ring();
```

Always check for `null` or use the null conditional operator (`?.`) when invoking events.

## **Asynchronous Delegates**

Delegates can be called asynchronously with `BeginInvoke` / `EndInvoke`, but in modern C#, use `async` and `Task` instead.

```csharp
Func<int, int, Task<int>> addAsync = async (a, b) =>
{
    await Task.Delay(100);
    return a + b;
};

var result = await addAsync(3, 4);
```

Or use delegates to orchestrate async pipelines:

```csharp
Func<HttpRequestMessage, Task<HttpResponseMessage>> pipeline = async request =>
{
    // logging, auth, etc.
    return await httpClient.SendAsync(request);
};
```

## **Delegates in LINQ**

LINQ heavily uses delegates — both `Func<T, bool>` and `Func<T, TResult>`.

```csharp
var names = new[] { "alice", "bob", "carol" };
var upper = names.Select(name => name.ToUpper()); // Func<string, string>
```

This allows for expressive and composable data transformation pipelines.

## **Real World Example: Retry Logic**

You can write reusable retry logic with delegates:

```csharp
public static T Retry<T>(Func<T> operation, int maxAttempts = 3)
{
    int attempts = 0;
    while (true)
    {
        try
        {
            return operation();
        }
        catch
        {
            if (++attempts >= maxAttempts)
                throw;
        }
    }
}
```

Usage:

```csharp
int result = Retry(() => UnreliableOperation());
```

## **Unit Testing with Delegates**

Using delegates instead of tightly coupled code makes testing easier.

Example:

```csharp
public class EmailSender
{
    private readonly Func<string, string, bool> _sendFunc;

    public EmailSender(Func<string, string, bool> sendFunc)
    {
        _sendFunc = sendFunc;
    }

    public bool Send(string subject, string body)
    {
        return _sendFunc(subject, body);
    }
}
```

In a unit test:

```csharp
var sender = new EmailSender((s, b) => s.Contains("Test"));
Assert.True(sender.Send("Test subject", "Hello"));
```

## **Combining Delegates Dynamically**

You can build pipelines by composing delegates:

```csharp
Func<string, string> step1 = s => s.ToUpper();
Func<string, string> step2 = s => $"Hello, {s}";
Func<string, string> pipeline = s => step2(step1(s));

var result = pipeline("patrick"); // "Hello, PATRICK"
```

This works well in middleware style architectures.

## **Custom Delegate Types vs Func/Action**

When to define your own delegate type:

* You want semantic clarity in public APIs.
    
* You want to use attributes (e.g., `[Obsolete]`) on delegate parameters.
    
* You need to support multicast scenarios with event like semantics.
    

Otherwise, `Func<>` and `Action<>` are sufficient for internal use and concise code.

---

Delegates are the backbone of flexible and composable C# applications. Mastering them enables clean separation of concerns, extensibility without inheritance & easy testability through injection. You also get functional patterns like pipelines and transformations that promote clean decoupled code.

While many developers use delegates indirectly via LINQ or events without thinking about it, going deeper into custom delegate usage, higher order functions, and strategies will improve your design skills in large systems.
