Microsoft released the newest version of the C# language, C#12. This new release has fresh improvements that make C# even better for developers, such as default values for lambda expressions.
To become familiar with this change, let’s dive into the topic and learn its benefits and drawbacks.
Lambda Improvements
The C# development team continues to enhance the lambda expression improvements started in C# 10. In C# 12 they made lambda expressions even more powerful by adding support for default values and params
arrays.
Before C# 12, we had to create local functions or use DefaultParameterValue
to work around the need for default values in lambda functions. Now, the new syntax allows us to create more concise and clear lambda functions. Another benefit is the ability to combine similar logic into a single lambda expression.
Before C#12
In .NET 7 and earlier, if we try to use a default parameter or params
in a lambda expression, the compiler raises errors:
For both situations, the compiler raises the warning CS9058 which stands for “Feature is not available. User newer language version.” In essence, the error indicates that we can’t use optional parameters or params
in these contexts.
Prerequisites For .NET 8
Since this is a new C# 12 feature, we need to be sure our development environment is up to date, to build and run the code from this article. If we don’t have it yet, we need to download and install the new .NET 8 SDK from Microsoft’s official website.
New Behavior In C#12
After updating our development environment, we should no longer have compilation errors when we define a default parameter in a lambda method declaration:
var greetUser = (string name = "User") => $"Hello, {name}!"; greetUser(); greetUser("Peter");
In this example, we define a lambda function and assign it to the variable greetUser
. Now, that C# accepts lambda default values, our method is more resilient when the parameter is not provided:
Hello, User! Hello, Peter!
For cases where we need to use params
in lambda definitions, the behavior is similar:
var greetTeam = (params string[] members) => $"Hello, team: {string.Join(", ", members)}"; greetTeam("John", "Peter", "Isaac"); greetTeam("John", "Peter", "Isaac", "Theo", "Matteo"));
Here, we create a new delegate that accepts a variable number of strings
as input and returns the formatted string
message. This way, we simplify the definition of our lambda expression and make it more flexible:
Hello, team: John, Peter, Isaac. Hello, team: John, Peter, Isaac, Theo, Matteo.
Breaking Changes
These improvements come with breaking changes, so we may encounter compilation errors if older code uses one of the previously allowed features. Specifically, the issue at hand concerns the use of var
to infer the type of method group. Since .NET SDK 7.0.200 and later, any application using the highlighted code no longer compiles:
void ExecuteAction(Action<string> act, string parameter) => act(parameter); void Hello(string name = "User") => Console.WriteLine($"Hello, {name}"); var helloDelegate = Hello; ExecuteAction(helloDelegate , "John"); int ExecuteFunc(Func<int[], int> func, params int[] parameter) => func(parameter); int Total(params int[] numbers) => numbers.Sum(); var totalDelegate = Total; ExecuteFunc(totalDelegate, new[] { 1, 2, 3 });
This is because the compiler cannot infer the correct type for the local variables helloDelegate
and totalDelegate
, so, the types are defined as anonymous delegates and the ExecuteAction
and ExecuteFunc
calls fail.
Instead, in place of using var
, we must explicitly define the delegate type:
void ExecuteAction(Action<string> act, string parameter) => act(parameter); void Hello(string name = "User") => Console.WriteLine($"Hello, {name}"); Action<string> helloDelegate = Hello; ExecuteAction(helloDelegate, "John"); int ExecuteFunc(Func<int[], int> func, params int[] parameter) => func(parameter); int Total(params int[] numbers) => numbers.Sum(); Func<int[], int> totalDelegate = Total; ExecuteFunc(totalDelegate, new[] { 1, 2, 3 });
Now, the highlighted lines explicitly define Action<string>
and Func<int[], int>
respectively, and the compiler processes the code as expected.
Conclusion
The changes in lambda expressions continue the improvements introduced by Microsoft in C# 10. Now, developers can create more resilient and flexible anonymous methods when defining lambda expressions with default values. Despite the possible breaking changes that we need to be aware of, the benefits of this improvement are worth it.