C#  

C# 14 Improved Lambda Expressions: Using ref, in, and out Parameters for High-Performance Code

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

  • Lambdas could not use ref, in or out directly

  • Workarounds required:

    • Local functions

    • Helper methods with custom delegates

Example:

  
    void Increment(ref int x) => x++;

Action<int> incrementLambda = x => x++; // Cannot use ref
delegate void ActionRef(ref int x);
ActionRef actionRef = Increment;
  
  • Verbose and harder to maintain

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
  
  • Updates elements in-place using ref

  • Efficient and concise for list or array transformations

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
  
  • Demonstrates direct modification of variables

  • Eliminates boilerplate temporary variables

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

  • 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,