C# Lambda Params: Syntax and Common Pitfalls
c# lambda params: Learn how to define lambda parameters in C#, including implicit and explicit types, multiple parameters, and working with params arrays.
When you write a lambda expression in C#, the parameter list is the part before the => operator. The way you declare those parameters affects type inference, readability, and how the lambda can be used with delegates. This article covers the syntax for c# lambda params, including implicit and explicit types, multiple parameters, and how to work with variable-length argument lists when you need them.
Basic Lambda Parameter Syntax
A lambda expression with a single parameter can omit parentheses:
Func<int, int> square = x => x * x;
The parameter x is implicitly typed because the compiler infers its type from the delegate signature. When a lambda has zero parameters, you must use empty parentheses:
Action printHello = () => Console.WriteLine("Hello");
For multiple parameters, parentheses are required and parameters are separated by commas:
Func<int, int, int> add = (a, b) => a + b;
The same rules apply whether you are using Func and Action delegates or assigning to a custom delegate type. The parameter list must match the delegate's signature in arity and type, though types can often be inferred.
Implicit and Explicit Parameter Types
In most cases, you can let the compiler infer the parameter types. This keeps the lambda concise. However, there are situations where you must specify types explicitly:
- When the compiler cannot infer the type from the delegate signature.
- When you want to improve readability for a complex signature.
- When you are using a lambda with a method that expects a delegate but the target type is not obvious.
Explicit types are written in parentheses:
Func<int, int> square = (int x) => x * x;
For multiple parameters:
Func<int, int, int> add = (int a, int b) => a + b;
You cannot use var for lambda parameters. The following code does not compile:
// Invalid: var is not allowed for lambda parameters Func<int, int> f = (var x) => x * x;
The compiler requires either an implicit type (no type annotation) or a concrete type name. If you specify a type for one parameter, you must specify types for all parameters in that lambda.
Using Params Arrays with Lambdas
The params modifier is not allowed directly on lambda parameters. You cannot write:
// Invalid: params is not permitted on lambda parameters Func<int[], int> sum = (params int[] numbers) => numbers.Sum();
However, you can achieve the same behavior by defining a delegate that uses a params array and then assigning a lambda to it. The lambda itself receives a regular array parameter; the params keyword lives on the delegate declaration.
delegate int SumDelegate(params int[] numbers); SumDelegate sum = numbers => numbers.Sum(); int result = sum(1, 2, 3); // result is 6
When you invoke the delegate, the compiler expands the arguments into an array. The lambda does not need to know about the params modifier. This pattern is useful when you want to pass a variable number of arguments to a method that accepts a delegate.
Common Mistakes with Lambda Parameters
One frequent mistake is forgetting parentheses when a lambda has zero or multiple parameters. For a single parameter, parentheses are optional, but for consistency many developers include them. Another mistake is mixing implicit and explicit types in the same parameter list. The compiler rejects a lambda where one parameter has a type and another does not.
Type inference can also fail when the lambda is assigned to a non-delegate type, such as Expression<TDelegate>. For expression trees, the compiler requires explicit parameter types because the expression tree must be built from the exact type information. For example:
Expression<Func<int, int>> expr = (int x) => x * x;
If you omit the type here, the compiler cannot infer it because the target type is an expression tree, not a delegate.
Another edge case is using a lambda with a ref or out parameter. Lambda parameters cannot have ref or out modifiers. If you need to pass a reference, you must use a local function or a method group instead.
Performance and Allocation Considerations
Lambda expressions are compiled into methods, and when they capture variables from the enclosing scope, the compiler generates a closure class. Each closure instance can cause heap allocation. The number of parameters does not directly affect allocation, but the capture of variables does. If a lambda captures no variables, the compiler can cache a single static delegate instance, reducing allocations.
When you use a params array with a delegate, each call that expands arguments into an array allocates a new array. This is the same behavior as any params method call. If you are calling such a delegate in a hot path, consider passing the array explicitly to avoid the implicit allocation.
For most applications, the allocation overhead is negligible. But if you are writing performance-sensitive code, measure the impact and consider alternatives like local functions, which may have different closure behavior.
Choosing Between Lambda and Local Function
Local functions were introduced in C# 7 and offer an alternative to lambdas for many scenarios. Unlike lambdas, local functions can have ref, out, and params parameters. They also support recursion and can be declared after their usage in the enclosing method. However, local functions are not delegates by themselves; you must convert them to a delegate if you need to pass them around.
A lambda is the right choice when you need to pass a callable as an argument to another method, such as with LINQ or event handlers. A local function is better when you need a helper that is only used within a single method and requires modifiers like params or ref. The decision often comes down to whether you need delegate semantics or just a local reusable routine.
For variable-length argument lists, if you need the params behavior inside a method, a local function is more direct:
int Sum(params int[] numbers) => numbers.Sum();
This local function can be called with Sum(1, 2, 3) directly, without an intermediate delegate. Choose the approach that fits the context: lambdas for delegate-based APIs, local functions for method-local helpers with special parameter modifiers.