C# Delegates and Lambda Expressions
Pass behavior as data, understand the method signature behind a lambda, and use Func, Action, and Predicate without hiding control flow.
Before this lesson
- Methods
- Generics
- Collections
Treat a method as a value with a checked signature
Read expression and statement lambdas
Choose named methods or lambdas based on clarity and reuse
The short answer
A delegate type describes a callable method signature. A lambda expression creates a short function value that can be stored, passed to another method, or used by collection and LINQ operations.
Some methods need a rule supplied by their caller
A Keep method can traverse numbers, but it cannot know whether a caller wants even values, positive values, or values above a threshold. Duplicating one method per rule repeats the traversal. Passing the rule lets one algorithm work with many behaviors.
A delegate is a type-safe reference to callable behavior. Predicate<int> represents a method that receives an int and returns bool. The compiler rejects a method or lambda whose parameter and return types do not match.
Read a lambda from left to right
In number => number >= 10, the left side names the parameter and the right side is the returned expression. Read it as: given a number, produce whether it is at least 10. The surrounding delegate type lets C# infer that number is int and the result is bool.
A statement lambda uses braces and can contain several statements. It needs an explicit return when its delegate expects a value. Keep small lambdas focused; move multi-step behavior into a named method so it can be explained, tested, and reused.
Func<decimal, decimal, decimal> addTax = (price, rate) =>
{
decimal tax = price * rate;
return price + tax;
};
Console.WriteLine(addTax(100m, 0.15m));Expected output
115.00
Func, Action, and Predicate cover common signatures
Func<T, TResult> represents a function that returns TResult; the final generic argument is always the return type. Action<T> accepts input and returns void. Predicate<T> accepts T and returns bool. You can also declare a custom delegate when a domain-specific name makes the contract clearer.
Events build on delegates to notify subscribers that something happened, such as an order being paid. The publisher controls when the event is raised while subscribers attach handlers. Event design is a follow-on topic; first become comfortable passing a single behavior into an ordinary method.
| Delegate | Shape | Example purpose |
|---|---|---|
| Func<int, string> | int → string | Format an identifier |
| Func<int, int, bool> | two ints → bool | Compare values |
| Action<string> | string → no result | Write a message |
| Predicate<Order> | Order → bool | Decide whether to keep an order |
Lambdas can capture surrounding variables
A lambda may use a local declared outside it. number => number >= minimum captures minimum. That is convenient, but the lambda now depends on surrounding state. If the state later changes, the observed behavior may change too.
Prefer a named method when behavior is reused, complex, or deserves a domain name. Prefer a short lambda when the behavior is local, obvious, and helps the call read as a sentence. Concision is a tool, not the goal.
int minimum = 10;
Predicate<int> isLargeEnough = number => number >= minimum;
Console.WriteLine(isLargeEnough(12));Expected output
True
Good habits
- Name lambda parameters for the domain: order rather than x.
- Avoid side effects inside filtering predicates.
- Extract a named method when a lambda needs comments to explain its rule.
Quick knowledge check
Answer before you reveal.
01In Func<string, int>, which type is the return value?
int, because the last Func type argument is the return type.
02Must every lambda be one line?
No. Statement lambdas can contain blocks, but substantial behavior is often clearer as a named method.
Practice challenge
Now build it without copying.
Write a Transform method that accepts List<decimal> and Func<decimal, decimal>. Use lambdas to apply a 10% discount and then to add a fixed service fee in separate calls.
You are done when
- Transform contains the loop only once
- Each supplied lambda matches the delegate signature
- The original list remains unchanged and a new result list is returned
Stretch: Pass a named method for one transformation and compare readability with the lambda call.
Open challenge in playgroundLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Is a lambda the same as an anonymous method?
Both create callable behavior without a separately named method. Lambda syntax is newer and integrates naturally with type inference and expression-based APIs.
What is a closure?
It is the combination of a function and captured surrounding variables whose lifetime may extend beyond the original scope. Captures are useful but can create hidden dependencies.
Can delegates refer to instance methods?
Yes. The delegate retains the target object and method, so invoking it calls that method on that instance.