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!"
▶️ 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;
}
}
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);
}
}
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;
}
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;
}
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)