C# Lambda vs Delegate: When to Use Each
c# lambda vs delegate: Understand the practical differences between C# lambda expressions and delegates, including syntax, variable capture, and allocation behavior, t...
c# lambda vs delegate requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In C#, the choice between a lambda expression and a delegate often appears when you need to pass behavior as an argument. A delegate is a type that defines a method signature and can hold a reference to any matching method. A lambda is a concise syntax for creating an anonymous method inline. Both can be assigned to delegate variables, but they differ in syntax, capture behavior, and how the compiler generates code.
What a Delegate Is in C#
A delegate is a reference type that encapsulates a method with a specific signature. You declare a delegate type, then create an instance that points to a method, and invoke it through the delegate.
public delegate int Operation(int a, int b); public static int Add(int a, int b) => a + b; Operation op = Add; int result = op(3, 4); // 7
The delegate type Operation can reference any method that takes two integers and returns an integer. This gives you a way to pass methods as parameters, store them in fields, or build callback mechanisms.
Lambda Expressions as Inline Anonymous Methods
A lambda expression is a compact way to write an anonymous method. It can be assigned to a delegate type, or to the predefined Func and Action delegate types.
Operation op = (a, b) => a + b; Func<int, int, int> func = (a, b) => a * b; Action<string> print = msg => Console.WriteLine(msg);
The lambda syntax is often shorter than declaring a named method, especially when the logic is simple and used in only one place. The compiler infers the parameter types from the delegate signature.
Method Groups and Anonymous Methods
Before lambdas, C# had anonymous methods using the delegate keyword. You can still use them, but lambdas are generally preferred for readability.
Operation op = delegate (int a, int b) { return a - b; };
A method group is the name of a method without parentheses. When you assign a method group to a delegate, the compiler performs a conversion.
Operation op = Add; // method group conversion
Method group conversion is useful when you already have a named method that matches the signature. It avoids writing a lambda that simply calls that method.
Variable Capture and Closure Behavior
A lambda can capture variables from the enclosing scope. This creates a closure, meaning the lambda retains access to those variables even after the method returns.
int offset = 10; Func<int, int> addOffset = x => x + offset; offset = 20; Console.WriteLine(addOffset(5)); // 25, not 15
The captured variable is the same variable, not a copy. This is important for event handlers and callbacks that need to read the current value of a local. The compiler generates a closure class to hold the captured variables, which has allocation and lifetime implications.
Delegates created from method groups do not capture local variables unless the method itself is an instance method that captures state via this. A static method group has no capture.
Performance and Allocation Differences
The runtime cost of using a lambda versus a delegate depends on how the compiler generates the code. A non-capturing lambda can be cached by the compiler as a static method and reused, so assigning it to a delegate repeatedly does not create a new delegate instance each time. A capturing lambda, however, allocates a closure object and a delegate instance each time the lambda is created.
Method group conversion also allocates a delegate instance, but if the method is static and the compiler can prove it, it may cache the delegate. For high-frequency code, prefer static lambdas or cached delegates to reduce allocations.
The invocation overhead of a delegate is a virtual call, which is slightly more expensive than a direct method call, but the difference is usually negligible unless you are invoking millions of times per second.
Choosing Between Lambda and Delegate in Practice
Use a lambda when:
- The logic is short and only needed in one place.
- You need to capture local variables.
- You are working with LINQ or other APIs that accept
FuncorAction.
Use a method group when:
- You already have a named method that matches the signature.
- The method is reused in multiple places.
- You want to avoid the extra indirection of a lambda that just calls another method.
Use an explicit delegate type when:
- You need a custom signature that isn't covered by
FuncorAction. - You want to give the delegate a meaningful name for readability.
- You are designing a public API that exposes a callback contract.
Common Pitfalls with Lambda and Delegate
One common mistake is capturing a loop variable. In older C# versions, capturing a for loop variable inside a lambda caused all closures to see the final value. In C# 5 and later, the foreach loop variable is per-iteration, but for still uses a single variable.
var actions = new List<Action>(); for (int i = 0; i < 3; i++) { actions.Add(() => Console.WriteLine(i)); } foreach (var action in actions) action(); // prints 3,3,3
To fix this, copy the loop variable into a local inside the loop.
Another pitfall is assuming delegate equality works across different delegate types. Two delegates with the same target and method are equal only if they have the same delegate type. Also, lambdas that are syntactically identical are not equal because they are different method instances.
Finally, remember that a lambda can be converted to an expression tree, but a delegate cannot. If you need to inspect or translate the code at runtime, use Expression<TDelegate> instead of a delegate type.