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 Are Parameter Modifiers in Lambdas?

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
  

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
  

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

FeatureBefore C# 14C# 14
Support for ref/in/outNoYes
Code verbosityHighLow
ReadabilityModerateHigh
Performance for large structsLowerImproved (in avoids copies)
MaintainabilityHarderEasier

Decision Guide: When to Use Parameter Modifiers in Lambdas

QuestionUse in LambdaAvoid in Lambda
Modify argument directlyrefNo
Avoid copying large structsinNo
Assign new valueoutNo
Working with simple, immutable dataAvoidUse normal lambda
Prioritizing readabilityYes, when necessaryAvoid overuse
Optional or performance-critical updatesYesNo

Benefits

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,