Cannot use ref or out parameter in lambda expressions

C#Lambda

C# Problem Overview


Why can't you use a ref or out parameter in a lambda expression?

I came across the error today and found a workaround but I was still curious why this is a compile-time error.

> CS1628: Cannot use in ref or out parameter 'parameter' inside an anonymous method, lambda expression, or query expression

Here's a simple example:

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    int newValue = array.Where(a => a == value).First();
}

C# Solutions


Solution 1 - C#

Lambdas have the appearance of changing the lifetime of variables that they capture. For instance the following lambda expression causes the parameter p1 to live longer than the current method frame as its value can be accessed after the method frame is no longer on the stack

Func<int> Example(int p1) {
  return () => p1;
}

Another property of captured variables is that changes to the variable are also visible outside the lambda expression. For example the following prints 42

void Example2(int p1) {
  Action del = () => { p1 = 42; }
  del();
  Console.WriteLine(p1);
}

These two properties produce a certain set of effects which fly in the face of a ref parameter in the following ways

  • ref parameters may have a fixed lifetime. Consider passing a local variable as a ref parameter to a function.
  • Side effects in the lambda would need to be visible on the ref parameter itself. Both within the method and in the caller.

These are somewhat incompatible properties and are one of the reasons they are disallowed in lambda expressions.

Solution 2 - C#

Under the hood, the anonymous method is implemented by hoisting captured variables (which is what your question body is all about) and storing them as fields of a compiler generated class. There is no way to store a ref or out parameter as a field. Eric Lippert discussed it in a blog entry. Note that there is a difference between captured variables and lambda parameters. You can have "formal parameters" like the following as they are not captured variables:

delegate void TestDelegate (out int x);
static void Main(string[] args)
{
    TestDelegate testDel = (out int x) => { x = 10; };
    int p;
    testDel(out p);
    Console.WriteLine(p);
}

Solution 3 - C#

You can but you must explicitly define all the types so

(a, b, c, ref d) => {...}

Is invalid, however

(int a, int b, int c, ref int d) => {...}

Is valid

Solution 4 - C#

As this is one of the top results for "C# lambda ref" on Google; I feel I need to expand on the above answers. The older (C# 2.0) anonymous delegate syntax works and it does support more complex signatures (as well closures). Lambda's and anonymous delegates at the very least have shared perceived implementation in the compiler backend (if they are not identical) - and most importantly, they support closures.

What I was trying to do when I did the search, to demonstrate the syntax:

public static ScanOperation<TToken> CreateScanOperation(
    PrattTokenDefinition<TNode, TToken, TParser, TSelf> tokenDefinition)
{
    var oldScanOperation = tokenDefinition.ScanOperation; // Closures still work.
    return delegate(string text, ref int position, ref PositionInformation currentPosition)
        {
            var token = oldScanOperation(text, ref position, ref currentPosition);
            if (token == null)
                return null;
            if (tokenDefinition.LeftDenotation != null)
                token._led = tokenDefinition.LeftDenotation(token);
            if (tokenDefinition.NullDenotation != null)
                token._nud = tokenDefinition.NullDenotation(token);
            token.Identifier = tokenDefinition.Identifier;
            token.LeftBindingPower = tokenDefinition.LeftBindingPower;
            token.OnInitialize();
            return token;
        };
}

Just keep in mind that Lambdas are procedurally and mathematically safer (because of the ref value promotion mentioned earlier): you might open a can of worms. Think carefully when using this syntax.

Solution 5 - C#

And maybe this?

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    var val = value; 
    int newValue = array.Where(a => a == val).First();
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionskalbView Question on Stackoverflow
Solution 1 - C#JaredParView Answer on Stackoverflow
Solution 2 - C#mmxView Answer on Stackoverflow
Solution 3 - C#Ben AdamsView Answer on Stackoverflow
Solution 4 - C#Jonathan DickinsonView Answer on Stackoverflow
Solution 5 - C#tickyView Answer on Stackoverflow