C# 14 introduces enhancements to lambda expressions, allowing parameter modifiers such as ref, in, and out directly in lambdas. This small feature greatly improves performance, readability, and consistency, especially for performance-critical operations, validation pipelines, and data transformations.
This article covers:
What parameter modifiers in lambdas are
How lambdas worked before C# 14
Practical and complex real-life scenarios
Input validation pipeline examples
Decision guide for when to use
Performance and maintainability considerations
What Are Parameter Modifiers in Lambdas?
C# 14 allows ref, in and out modifiers in lambda parameters
Control how arguments are passed:
ref → by reference, can read/write
in → by reference, read-only
out → by reference, must be assigned
Enables consistent lambda behavior with methods
Reduces boilerplate compared to older workarounds
Before C# 14: Lambda Limitations
Example:
void Increment(ref int x) => x++;
Action<int> incrementLambda = x => x++; // Cannot use ref
delegate void ActionRef(ref int x);
ActionRef actionRef = Increment;
C# 14: Lambda Parameter Modifiers in Action
ref Lambda
Action<ref int> doubleValue = (ref int x) => x *= 2;
int val = 5;
doubleValue(ref val);
Console.WriteLine(val); // Output: 10
in Lambda
Action<in int> printValue = (in int x) => Console.WriteLine(x);
int num = 42;
printValue(num); // Output: 42
out Lambda
Func<string, out int, bool> tryParse = (string s, out int result) => int.TryParse(s, out result);
tryParse("123", out int value);
Console.WriteLine(value); // Output: 123
Real-Life Scenario 1: Performance-Critical Math
Func<in double, in double, double> add = (in double x, in double y) => x + y;
double a = 3.5, b = 2.5;
Console.WriteLine(add(a, b)); // Output: 6.0
in avoids unnecessary copies for large structs
Useful in physics engines, graphics calculations, and numeric simulations
Real-Life Scenario 2: Batch Updates in Collections
List<int> numbers = new() { 1, 2, 3, 4 };
Action<ref int> square = (ref int x) => x *= x;
for (int i = 0; i < numbers.Count; i++)
{
square(ref numbers[i]);
}
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 4, 9, 16
Real-Life Scenario 3: Input Validation Pipeline
// Trim and validate input
Func<string, out string, bool> trimValidate = (string input, out string result) =>
{
result = input?.Trim() ?? "";
return !string.IsNullOrEmpty(result) && result.Length <= 20;
};
// Mask sensitive data
Action<ref string> maskSensitive = (ref string data) =>
{
if (data.Length > 4)
data = new string('*', data.Length - 4) + data[^4..];
};
string[] inputs = { " 1234567890 ", "abcd", " ", "98765" };
foreach (var input in inputs)
{
if (trimValidate(input, out string cleaned))
{
maskSensitive(ref cleaned);
Console.WriteLine($"Processed input: {cleaned}");
}
else
{
Console.WriteLine("Invalid input");
}
}
Output:
Processed input: ******7890
Processed input: abcd
Invalid input
Processed input: *8765
Combines out and ref lambdas in a realistic input pipeline
Useful for forms, API inputs, and secure data handling
Complex Scenario: Async Service with Optional Cache
Func<ref int, bool> incrementCache = (ref int x) => { x++; return true; };
int cacheValue = 0;
incrementCache(ref cacheValue);
Console.WriteLine(cacheValue); // Output: 1
Comparison: Before vs C# 14 Lambdas
| Feature | Before C# 14 | C# 14 |
|---|
| Support for ref/in/out | No | Yes |
| Code verbosity | High | Low |
| Readability | Moderate | High |
| Performance for large structs | Lower | Improved (in avoids copies) |
| Maintainability | Harder | Easier |
Decision Guide: When to Use Parameter Modifiers in Lambdas
| Question | Use in Lambda | Avoid in Lambda |
|---|
| Modify argument directly | ref | No |
| Avoid copying large structs | in | No |
| Assign new value | out | No |
| Working with simple, immutable data | Avoid | Use normal lambda |
| Prioritizing readability | Yes, when necessary | Avoid overuse |
| Optional or performance-critical updates | Yes | No |
Benefits
Clean, expressive syntax for ref , in , and out
Better performance for large structs or repeated transformations
Enables concise, maintainable pipelines
Ideal for validation, parsing, batch processing, and numeric operations
Conclusion
C# 14’s improved lambda expressions with parameter modifiers simplify writing high-performance, expressive, and maintainable code . They are particularly useful for input validation pipelines, batch processing, numeric computations, and optional updates , while regular lambdas remain ideal for simple, immutable data. Using ref , in , and out in lambdas helps create modern, efficient, and readable C# code.
Happy Coding!
I write about modern C#, .NET, and real-world development practices. Follow me on C# Corner for regular insights,