Back to Blog
C#

C# Lambda Default Parameters: Syntax and Limits

c# lambda default parameters: Learn how to use default parameter values in C# lambda expressions, including C# 12 syntax, limitations, and alternatives for older versi...

C#LambdaDefault ParametersC# 12Delegates
Diagram showing a C# lambda expression with a default value assigned to a parameter, highlighting C# 12 syntax.

C# lambda default parameters let you specify default values for lambda parameters, a feature introduced in C# 12. This article covers the syntax, behavior, and limitations of C# lambda default parameters, along with practical alternatives for projects on older language versions.

C# 12 Syntax for Lambda Default Parameters

A lambda expression can specify a default value for any of its parameters by placing the value after the parameter name and an equals sign:

var add = (int a, int b = 10) => a + b;

The compiler treats b as an optional parameter. Calling add(5) produces the same result as add(5, 10). The default value must be a compile-time constant, exactly as with method parameters. You cannot use a variable, a property, or a method call as the default.

Parameter order matters: any parameter with a default value must appear to the right of parameters that do not have defaults. The following declaration is invalid:

var invalid = (int a = 1, int b) => a + b; // CS1763: Optional parameters must appear after all required ones

The rule matches the behavior of optional parameters in named methods and prevents ambiguous calls.

How Default Values Behave at the Call Site

When you invoke a lambda that has default parameters, you can omit the trailing arguments. The compiler substitutes the default values at the call site, not at runtime. This means the default value is baked into the generated code, and changing the default value requires recompiling all callers that rely on it.

var greet = (string name, string greeting = "Hello") => $"{greeting}, {name}!"; Console.WriteLine(greet("Alice")); // Hello, Alice! Console.WriteLine(greet("Bob", "Hi")); // Hi, Bob!

You can also use named arguments to skip a parameter that has a default, though this is less common with lambdas:

var format = (int value, string prefix = "Value: ", bool uppercase = false) => uppercase ? prefix.ToUpper() + value : prefix + value; Console.WriteLine(format(42, uppercase: true)); // VALUE: 42

Named arguments work because the compiler knows the parameter names from the lambda's signature.

Compile-Time Constraints and Error Cases

Default values in lambda parameters must be compile-time constants. This excludes null for reference types? Actually null is a constant, so it's allowed. But you cannot use Environment.TickCount or DateTime.Now. The compiler enforces this with error CS1736.

Another constraint: you cannot mix optional and required parameters in a way that makes the call ambiguous. For example, you cannot have two parameters with defaults and then call with only the second one without using a named argument, because the compiler would try to fill the first.

The feature is only available in C# 12 and later. If you compile with an older language version, you get a syntax error. The runtime does not need to support it; it is purely a compiler feature. The generated code uses the default values directly.

Workarounds for C# Versions Before 12

If your project targets an older C# version, you have two practical options for achieving similar behavior.

The first is to use a delegate type that declares default parameter values. The lambda assigned to the delegate must match the delegate's signature, but the defaults are defined on the delegate, not on the lambda itself:

public delegate int Operation(int a, int b = 10); Operation add = (a, b) => a + b; Console.WriteLine(add(5)); // 15

The lambda itself does not declare defaults; the delegate type supplies them. This works because the compiler applies the delegate's default values when the delegate is invoked. The limitation is that the defaults are fixed by the delegate type, and you cannot have different lambdas with different defaults for the same delegate type.

The second approach is to use nullable parameters and check for null inside the lambda:

var add = (int a, int? b) => a + (b ?? 10);

This gives you runtime flexibility, but the caller must still pass null explicitly unless you also define a separate overload. It does not provide the same call-site convenience as true optional parameters.

Practical Use Cases for Lambda Default Parameters

Lambda default parameters are most useful when you define inline callbacks or configuration functions where a common fallback value is natural. For example, a logging callback that can optionally include a timestamp:

var log = (string message, string? level = "INFO") => Console.WriteLine($"[{level}] {message}");

They also simplify test helpers and small factory functions where you want to avoid overloads. Because the default is part of the lambda's signature, you can store the lambda in a variable and call it consistently.

One area to be careful with is expression trees. Lambda expressions assigned to Expression<TDelegate> cannot have default parameter values. The expression tree syntax does not support optional parameters, and the compiler rejects such a lambda with error CS0854. If you need expression trees, stick to delegates without defaults.

Compatibility and Maintainability Considerations

Using lambda default parameters couples your code to C# 12. If your library is consumed by projects on older language versions, the source code that contains the lambda will not compile for them. Even if you ship a compiled assembly, the consumers' compiler must be able to parse the source if they use the library as source (e.g., in a source generator). Most consumers compile against a library, so the language version of the library's source matters only when building it. However, if you are writing a source generator or a template that emits C# code, you must consider the target language version.

From a maintainability perspective, default values on lambdas can hide important configuration. A reader may not immediately see that a callback has optional behavior. It is often clearer to use a separate overload or a delegate with defaults, because the defaults are visible at the type level. For small, local lambdas, the convenience usually outweighs the readability cost.

Choosing Between Lambda Defaults and Delegate Defaults

The decision comes down to where you want the default to live. If the default belongs to a specific lambda instance, use C# 12 lambda default parameters. If the default is a property of the delegate contract itself, define it on the delegate type. The table below summarizes the tradeoffs:

ApproachDefault locationC# versionCall-site flexibilityExpression tree support
Lambda defaultLambda signatureC# 12+Per lambdaNo
Delegate defaultDelegate typeAllFixed per delegateNo
Nullable parameterLambda bodyAllCaller must pass nullYes (with care)

Use lambda defaults when you need different defaults for different lambdas that share the same delegate type. Use delegate defaults when the default is an invariant part of the callback contract. Use nullable parameters when you need expression tree support or when the default must be computed at runtime.

c# lambda default parameters: Practical Usage and Code Examp | RYUSLOG DEV