A delegate is a type that represents a method signature. A variable of a delegate type holds a reference to a method with that signature, and calling the variable calls the method. This is how C# treats functions as values: you can store them, pass them to other methods and call them later.
Output:
40
70
68.00
delegate decimal PriceRule(decimal price); declares a new type, the same way class does. Any method that takes one decimal and returns a decimal fits it. The variable rule pointed at three different methods in turn, and each call ran whichever one it held.
Declaring, Creating and Invoking
A delegate type declaration looks like a method signature with the delegate keyword in front. It usually sits at namespace level, beside classes, or nested inside a class.
There are four ways to create a delegate instance:
PriceRule a = HalfPrice; // method group (most common)
PriceRule b = new PriceRule(HalfPrice); // explicit constructor, same thing
PriceRule c = p => p - 5m; // lambda expression
PriceRule d = delegate (decimal p) { return p; }; // anonymous method (C# 2 syntax)
And two ways to call it, which are identical: rule(80m) and rule.Invoke(80m). The compiler checks the signature at assignment time, so a mismatched method is a compile error rather than a runtime surprise.
A delegate can point to an instance method too. It then remembers both the method and the object to call it on:
Output:
Ana got: Your order shipped
target: Mailbox, method: Receive
Target is the object the delegate will call the method on (null for a static method), and Method describes the method. Holding the target also means a delegate keeps that object alive, which matters for events.
Delegates as Callbacks
The main job of a delegate is to let a method call code that its caller chose. The method defines when something happens; the caller defines what:
Output:
[## ] 25%
[##### ] 50%
[####### ] 75%
[##########] 100%
import finished
ImportRows knows nothing about consoles or bars. The same method can drive a progress bar, a log line or a UI update, depending on what the caller passes. List.Sort(Comparison<T>), Array.Find(Predicate<T>), Task.Run(Action) and every LINQ operator follow this pattern.
Multicast Delegates: += and -=
A delegate can hold more than one method. += appends a method to its invocation list, -= removes it, and invoking the delegate calls every method in order:
Output:
email sent for A-1001
stock updated for A-1001
warehouse notified for A-1001
3 handlers
email sent for A-1002
warehouse notified for A-1002
Three details of multicast delegates matter in practice:
- Return values: if the delegate type returns a value, invoking a multicast delegate returns only the last method's result. To collect all results, loop over
GetInvocationList()and invoke each one yourself. - Exceptions: if one method throws, the remaining methods do not run and the exception reaches the caller.
- Immutability: delegates are immutable.
+=creates a new delegate with a longer list and assigns it back to the variable, which is why removing with-=on a copy does not affect the original.
Removing a lambda with -= only works if you pass the same delegate instance. Writing the same lambda twice creates two different delegates, so handlers -= id => Log(id); removes nothing. Store the lambda in a variable when you plan to unsubscribe it.
Null Delegates and ?.Invoke
A delegate variable with nothing assigned is null, and removing the last method with -= also leaves null. Invoking null throws NullReferenceException:
Output:
no handler, no crash
saved draft.txt
NullReferenceException
?.Invoke is the standard way to call an optional callback. The ?. operator cannot be put directly before the parentheses (onSaved?("x") is not valid syntax), so the pattern calls the delegate's Invoke method by name, which does the same as the shorthand call.
Delegates, Func, Action and Events
Declaring a delegate type for every signature would be tedious, so .NET ships generic ones:
| Built-in type | Equivalent custom declaration |
|---|---|
Action | delegate void Action(); |
Action<T> | delegate void Action<T>(T arg); |
Func<TResult> | delegate TResult Func<TResult>(); |
Func<T, TResult> | delegate TResult Func<T, TResult>(T arg); |
Predicate<T> | delegate bool Predicate<T>(T obj); |
PriceRule from the first example is the same shape as Func<decimal, decimal>, yet the two are distinct types and do not convert into each other. Even two built-in types with identical signatures do not:
Func<int, bool> isPositive = x => x > 0;
Predicate<int> p = isPositive;
// error CS0029: Cannot implicitly convert type 'System.Func<int, bool>' to 'System.Predicate<int>'
Predicate<int> ok = new Predicate<int>(isPositive); // wrap it explicitly
Predicate<int> ok2 = x => isPositive(x); // or with a lambda
A lambda converts to any compatible delegate type, so APIs usually take lambdas and the question never comes up. The Func and Action page covers the generic family and when a custom declaration is still worth it.
Events are built directly on delegates. An event field is a multicast delegate that outside code may only += and -=, never invoke or overwrite. The subscriber list in the multicast example above is exactly what an event manages for you, with that extra protection. Events are covered on their own page.
Frequently Asked Questions
What is a delegate in C#?
A delegate is a type-safe reference to a method. You declare a delegate type with a signature, such as delegate decimal PriceRule(decimal price);, and any method with that signature (static, instance, or a lambda) can be stored in a variable of that type and called later through it. Delegates are how C# passes functions as values.
What is the difference between a delegate and Func in C#?
Func<T, TResult> and Action<T> are delegate types that .NET already declares for you, generic over the parameter and return types. A custom delegate declaration gives the type a name and parameter names that document its role, and supports ref, out and in parameters. For most code, Func and Action are enough.
What is a multicast delegate in C#?
A delegate that holds several methods. += adds a method to its invocation list and -= removes one; invoking the delegate calls them all in order. If the delegate returns a value, the caller only receives the last method's result, and an exception in one method stops the rest from running.
How do I safely invoke a delegate that might be null?
Use the null-conditional operator: onProgress?.Invoke(50);. A delegate variable with no methods assigned is null, and calling it directly throws NullReferenceException. ?.Invoke reads the variable once, so it is also safe if another thread removes the last handler at the same moment.
When should I declare my own delegate type instead of using Func or Action?
When the signature needs ref, out or in parameters (Func and Action cannot express them), when a named type makes a public API clearer (delegate bool Validator(string input)), or when you want parameter names to show up in IntelliSense. Otherwise prefer Func and Action, which every .NET developer recognizes.