DEV Community

Ellie
Ellie

Posted on

Delegates — A Simple Introduction

I think of a delegate as a reference to a function. It’s a type that says “I can point at any method that has this parameter list and this return type.” You create one, point it to a compatible method, and call the method through the delegate. Because you can pass delegates around, they’re great for plugging behaviour into APIs. C# events are built on delegates too.

Real-World Analogy: Delegates as a Phone Number

Think of a delegate like a phone number you hand to someone with simple instructions: “when It's done, call this number and say something”
The caller doesn’t need to know who picks up or what they do—just that dialing the number (invoking the delegate) with the message will trigger the right action. If you later give them a different number (swap the delegate), they dial the same way but reach someone else who can handle it differently, without them changing their routine.

Syntax at a glance

// 1) Declare a delegate type
public delegate string Greeter(string name);


// 2) Point it at compatible methods
static string Formal(string name) => $"Hello, {name}.";
static string Friendly(string name) => $"Hey {name}!";


// 3) Invoke through the delegate instance
Greeter g = Formal; // reference to a function
Console.WriteLine(g("Ava")); // "Hello, Ava."


g = Friendly; // swap behaviour on the fly
Console.WriteLine(g("Ava")); // "Hey Ava!"
Enter fullscreen mode Exit fullscreen mode

▶️ Tiny runnable sample

welldone if you have reached here, bellow I'm trying to explain the delegate in an example more closed to real world

Scenario: train ticket promos with delegates

Imagine you have a Train ticket and you want to add some promo code which apply some discount to your final price.
let's write this code without delegates and then with delegates.

Basics:

First Lets have TrainTicket Model:

public class TrainTicket
{
    public string TicketId { get; }
    public decimal Price { get; set; }      
    public decimal Discount { get; set; }   // from promo
    public decimal FinalPrice => Price - Discount;

    public TrainTicket(string ticketId, decimal basePrice)
    {
        TicketId = ticketId;
        Price = basePrice;
    }
}
Enter fullscreen mode Exit fullscreen mode

and now Let's consider we have these Rules for the promos:

public static class PromoRules
{
    public static decimal TenOff(decimal price)
    {
        return Math.Min(10m, price);
    }
    public static decimal HalfOff(decimal price)
    {
        return price * 0.50m;
    } 
    public static decimal LoyalCap(decimal price)
    {
        return Math.Min(price * 0.05m, 5m);
    }
}
Enter fullscreen mode Exit fullscreen mode

Apply promo Without Delegate

We get a promoCode as a string and choose the rule with a switch:

public static TrainTicket ApplyPromo(TrainTicket ticket, string? promoCode)
    {
        switch (promoCode)
        {
            case "TENOFF": // flat £10 off, capped by price
                ticket.Discount = PromoRules.TenOff(ticket.Price);
                break;
            case "HALF":   // 50% off
                ticket.Discount = PromoRules.HalfOff(ticket.Price);
                break;
            case "LOYAL":  // 5% off, max £5
                ticket.Discount = PromoRules.LoyalCap(ticket.Price);
                break;
            default:
                // unknown code -> no discount
                break;
        }

        return ticket;
    }

Enter fullscreen mode Exit fullscreen mode

Apply promo With Delegate

what would make the code more simple would be a way to avoid having this switch statement and figuring out a way which make it easier to determind which rule fits which promo code.
Here delegate would help us to defind this relationship and applying the rules.

public delegate decimal PromoRuleDelegate(decimal price);

// single place to add/remove codes
    private static readonly (string Code, PromoRuleDelegate Rule)[] PromoRuleMap = new[]
    {
        ("TENOFF", PromoRules.TenOff),
        ("HALF",   PromoRules.HalfOff),
        ("LOYAL",  PromoRules.LoyalCap),
        // add more 
    };

public static TrainTicket ApplyPromo(TrainTicket ticket, string? promoCode)
{
        foreach (var promoRule in PromoRuleMap)
        {
            if (promoRule.Code == promoCode) 
            {
                   ticket.Discount = promoRule.Rule(ticket.Price);
                   break;
            }
        }

        return ticket; 
    }

Enter fullscreen mode Exit fullscreen mode

Why this helps

  • You keep rule selection logic in one place while the call-site stays simple.
  • Adding a new promo becomes: write a method, add one tuple to PromoRuleMap.

▶️ Runnable sample: Apply Promo Without Delegate
▶️ Runnable sample: Apply Promo With Delegate

Top comments (0)