Lambdas are everywhere in modern C#: LINQ, ASP.NET Core minimal APIs, callbacks and configuration.
A delegate is a type-safe reference to a method. You rarely declare your own; the built-in generic delegates cover most needs:
Func<T1, ..., TResult>takes arguments and returns a value:Func<int, int> square.Action<T1, ...>returns nothing.Predicate<T>is an olderFunc<T, bool>.
A lambda expression creates a delegate inline: x => x * x, (a, b) => a + b, or a block x => { var y = x * 2; return y; }. Since C# 10, lambdas get a natural type, so var add = (int a, int b) => a + b; works, and they can have default parameter values (C# 12).
Lambdas capture variables from the enclosing scope (a closure). The variable itself is captured, not its value at that time, so later changes are visible. Use static x => ... to forbid captures when you want to avoid accidental allocations.
Method groups convert to delegates too: names.ForEach(Console.WriteLine);.
An event is a delegate field that outsiders can only subscribe to (+=) and unsubscribe from (-=); only the owning class can raise it. The convention is event EventHandler<TArgs>? Changed; raised with Changed?.Invoke(this, args). Forgetting to unsubscribe from long-lived publishers keeps subscribers alive, a classic memory leak.
Example
Func<int, int> square = x => x * x;
Action<string> shout = s => Console.WriteLine(s.ToUpper());
var add = (int a, int b = 1) => a + b;
Console.WriteLine(square(5)); // 25
shout("hi"); // HI
Console.WriteLine(add(2)); // 3
int counter = 0;
Action inc = () => counter++; // captures the variable
inc(); inc();
Console.WriteLine(counter); // 2
static int Apply(Func<int, int> f, int v) => f(v);
Console.WriteLine(Apply(square, 3)); // 9
var t = new Thermostat();
t.Changed += (_, temp) => Console.WriteLine($"now {temp}");
t.Set(21); // now 21
class Thermostat
{
public event EventHandler<int>? Changed;
public void Set(int value) => Changed?.Invoke(this, value);
}Remember
- Func returns a value, Action does not
- Lambdas capture variables, not snapshots of their values
- Events allow outside code to subscribe with += but not raise
- Unsubscribe from long-lived events to avoid leaks
Watch out for
- Capturing a loop variable of a for loop in lambdas and seeing its final value in all of them.