Intermediate · Exceptions, delegates, events and async · topic 27 of 43 · 30 min
Delegates, lambdas, Func, Action and Predicate
Explanation
Why pass methods around?
Sometimes the "what to do" changes but the "how to loop" stays the same. A sort needs to know how to compare. A filter needs to know which items to keep. A retry helper needs to know which operation to retry. Delegates let the caller pass that changing piece in as a parameter. LINQ's Where and Select work exactly this way.
A delegate type, the old way
PriceRule rule = Festive; // store a method in a variable
Console.WriteLine(rule(1000m)); // 900.0
rule = NoChange;
Console.WriteLine(rule(1000m)); // 1000
static decimal Festive(decimal p) => p * 0.9m;
static decimal NoChange(decimal p) => p;
delegate decimal PriceRule(decimal price); // type declarations go after top-level statementsFunc, Action and Predicate
| Built-in type | Meaning | Example |
|---|---|---|
| Func<TResult> | No inputs, returns TResult | Func<DateTime> now = () => DateTime.Now; |
| Func<T1, TResult> | One input, returns a value (the LAST type is the return) | Func<int, int> square = n => n * n; |
| Func<T1, T2, TResult> | Two inputs, returns a value | Func<decimal, decimal, decimal> add = (a, b) => a + b; |
| Action<T> | Takes inputs, returns nothing | Action<string> log = msg => Console.WriteLine(msg); |
| Predicate<T> | Takes one input, returns bool | Predicate<int> isEven = n => n % 2 == 0; |
Lambda syntax
Func<int, int> twice = x => x * 2; // expression lambda
Func<int, int, int> add = (a, b) => a + b; // two parameters
Func<string> hello = () => "Namaste"; // no parameters
Func<decimal, decimal> withGst = amount => // statement lambda
{
decimal gst = amount * 0.18m;
return amount + gst;
};
var cube = (int n) => n * n * n; // C# 10: natural type, Func<int,int>
Console.WriteLine(twice(5) + add(2, 3)); // 15
Console.WriteLine(withGst(100m)); // 118.00Passing behaviour as a parameter
static List<T> Filter<T>(List<T> items, Predicate<T> keep)
{
var result = new List<T>();
foreach (T item in items)
if (keep(item)) result.Add(item);
return result;
}
var marks = new List<int> { 35, 82, 47, 91, 28 };
var passed = Filter(marks, m => m >= 40);
Console.WriteLine(string.Join(", ", passed)); // 82, 47, 91Closures: lambdas remember variables
decimal rate = 0.05m;
Func<decimal, decimal> tax = amt => amt * rate;
rate = 0.12m; // the lambda sees the change
Console.WriteLine(tax(100m)); // 12.00var actions = new List<Action>();
for (int i = 0; i < 3; i++)
actions.Add(() => Console.Write(i));
foreach (var a in actions) a(); // prints 333Multicast delegates
Action<string> notify = msg => Console.WriteLine($"SMS: {msg}");
notify += msg => Console.WriteLine($"Email: {msg}");
notify("Order shipped");Common mistakes and fixes
- Mixing up Func and Action: if it returns something, use Func, and remember the LAST type parameter is the return type.
- Calling a null delegate: NullReferenceException. Use handler?.Invoke(...).
- Capturing a for-loop variable in lambdas: copy it to a local inside the loop.
- Huge statement lambdas: if a lambda is longer than a few lines, make it a named method and pass the method name.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Delegates, lambdas, Func, Action and Predicate · 1/7
In this lesson you will pass methods around like data.
Prefer a video by a teacher? Find free videos on “C# delegates lambda expressions Func Action Predicate tutorial” on YouTube ↗
In real life: Pluggable discount rules for a festival sale
During Diwali a shopping site runs several discounts: 10% off electronics, Rs 100 off above Rs 1,000, and free shipping on certain items. The checkout keeps a list of rules as Func delegates and applies them in order, so marketing can add or remove rules without changing the checkout code.
var rules = new List<Func<decimal, decimal>>
{
price => price >= 1000m ? price - 100m : price,
price => Math.Round(price * 0.9m, 2),
};
decimal Apply(decimal price)
{
foreach (var rule in rules) price = rule(price);
return price;
}
Console.WriteLine(Apply(1500m)); // 1500 - 100 = 1400, then 10% off = 1260.0
Console.WriteLine(Apply(800m)); // no flat discount, 10% off = 720.0Practice (0/4 done)
Func<int, int, int> f = (a, b) => a * b + 1; What is f(3, 4)?
Which type fits a method that takes a string and returns nothing?
int x = 1; Func<int> get = () => x; x = 5; Console.WriteLine(get());
Write a method Retry(Func<bool> operation, int times, Action<int> onFail) that calls operation up to times times, stops when it returns true, and calls onFail(attemptNumber) after each failure. Test it with a lambda that fails twice and then succeeds (use a counter captured by the lambda).
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Events: letting objects notify each otherExceptions, delegates, events and async · same module
- async and await: waiting without blockingExceptions, delegates, events and async · same module
- Lambdas and higher-order functionsKotlin and Android App Development · Null safety, functions and lambdas
- Variadic functions, function values and closuresGo Programming (backend and cloud) · Control flow and functions
- Lambdas and functional interfacesJava Programming (Core to Spring Boot) · Lambdas, streams and files
- Lambdas: small functions written in placeC++ Programming (modern C++ and DSA) · Modern C++: lambdas, C++17 and C++20 essentials
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
In Func<string, int>, what is the return type?
Answer: int
The last type parameter of Func is the return type.
What is a closure?
Answer: A lambda that captures variables from the surrounding code
Captured variables live on as long as the lambda does.
Predicate<int> p = n => n > 10; What is p(7)?
Answer: false
7 > 10 is false.
Action a = () => Console.Write("X"); a += () => Console.Write("Y"); a(); prints...
Answer: XY
Multicast delegates call each method in the order added.
What is the safe way to call a delegate that might be null?
Answer: handler?.Invoke()
?.Invoke does nothing when the delegate is null.
Which call uses trailing lambda syntax?Related
Answer: list.filter { it > 0 }
The lambda moves outside the parentheses.
Inside func f(names ...string), what is the type of names?Related
Answer: []string
A variadic parameter is a slice inside the function.
When did lambdas arrive in Java?Related
What makes a lambda "generic"?Related
Answer: It uses auto in its parameter list
auto parameters turn the call operator into a template.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Exceptions, delegates, events and async” before its exam: Events: letting objects notify each other, async and await: waiting without blocking, Files and JSON with System.Text.Json.
- Module 7 exam: Exceptions, delegates, events and async · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Web APIs with ASP.NET Core and EF Core. 43 topics left in the course before the final exam.
← Previous: Exceptions: try, catch, finally and custom exceptions