Lambda Default Parameters in Modern C
Modern C#, introduced in C# 12, allows developers to declare default values for lambda expression parameters just as they do with standard methods. This article explains how default parameter values in lambda expressions work, how type inference handles them, how they behave under the hood, and the rules governing their usage.
Syntax and Basic Usage
Prior to C# 12, lambda expressions could not declare default parameter values; all parameters had to be supplied explicitly during invocation. Starting with C# 12, you can assign default values to one or more parameters directly within the lambda parameter list.
var increment = (int source, int step = 1) => source + step;
Console.WriteLine(increment(5)); // Outputs: 6
Console.WriteLine(increment(5, 3)); // Outputs: 8Just like standard method declarations, default values must be compile-time constants, and optional parameters must appear after all required parameters in the parameter list.
Type Inference and Natural Delegate Types
The C# compiler determines how to handle default parameters based on
whether the lambda is assigned to a implicitly typed variable
(var) or an explicitly typed delegate.
When you assign a lambda to var, the compiler generates
a "natural delegate type" that includes metadata about the default
parameter values:
var greet = (string name = "Guest") => $"Hello, {name}!";
greet(); // Valid: uses default "Guest"However, if you explicitly assign the lambda expression to a standard
Func<...> or Action<...> delegate,
the default value functionality is lost at the call site:
Func<string, string> greetFunc = (string name = "Guest") => $"Hello, {name}!";
// greetFunc(); // Compilation Error: No overload for method 'Invoke' takes 0 arguments
greetFunc("Alice"); // ValidThis happens because the predefined
Func<T, TResult> delegate definition has no concept
of default parameters on its Invoke method. To retain
invocation with default values, rely on var or declare a
custom delegate type that mirrors the default values.
Compiler Implementation Under the Hood
When the compiler generates a natural delegate for a lambda with
default parameters, it decorates the delegate's Invoke
method parameters with the standard
[System.Runtime.InteropServices.Optional] and
[System.Runtime.InteropServices.DefaultParameterValue]
attributes.
At the call site, the compiler inspects the delegate signature. If an argument is omitted, the compiler substitutes the constant value defined in the metadata directly into the emitted Intermediate Language (IL) call instruction.
Working with
params Collections
Alongside default parameter values, modern C# also allows the
params modifier inside lambda expressions. You can combine
default parameters and parameter arrays within the same lambda:
var formatList = (string prefix = "Item", params int[] values) =>
$"{prefix}: {string.Join(", ", values)}";
Console.WriteLine(formatList()); // Outputs: Item:
Console.WriteLine(formatList("Values", 1, 2, 3)); // Outputs: Values: 1, 2, 3Key Limitations and Rules
- Compile-Time Constants Only: Default parameter
values must be resolvable at compile time (literals,
constvalues,default, ornull). - Trailing Position: Parameters with default values must always follow required parameters.
- Expression Trees: Expression trees
(
Expression<TDelegate>) do not support lambdas with default parameter values orparamsarrays.