209

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();
}
Colonel Panic
  • 132,665
  • 89
  • 401
  • 465
skalb
  • 5,357
  • 3
  • 26
  • 23
  • 20
    May I ask what was the workaround that you had found ? – Beatles1692 Feb 22 '12 at 13:07
  • It's about iterators, but much of the same reasoning in this post (also by Eric Lippert — he is on the language design team after all) applies to lambdas: – Joel Coehoorn Sep 02 '09 at 03:35
  • 4
    You can just declare a local normal variable and work with that, and assign the result to value afterwards... Add a var tempValue = value; and then work with tempValue. – Drunken Code Monkey Nov 13 '18 at 05:47
  • The article @JoelCoehoorn's [comment](https://stackoverflow.com/questions/1365689/cannot-use-ref-or-out-parameter-in-lambda-expressions#comment19679969_1365689) refers to can now be found [here](https://learn.microsoft.com/en-us/archive/blogs/ericlippert/iterator-blocks-part-two-why-no-ref-or-out-parameters) – Bill Tür stands with Ukraine Oct 19 '20 at 14:05
  • 1
    @Beatles1692: This is the workaround that I found: https://stackoverflow.com/a/75362003/2505186 – Tobias Knauss Feb 06 '23 at 13:37

6 Answers6

141

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 variables are also visible outside the lambda expression. For example, the following code prints out 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.

Seyfi
  • 1,832
  • 1
  • 20
  • 35
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 4
    if you still want to use it, then you can create a temp variable and use that inside lamda. something like `int tempVariable = refVariable; int newValue = array.Where(a => a == tempVariable).First();` – sree Sep 21 '20 at 07:34
92

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);
}
marsze
  • 15,079
  • 5
  • 45
  • 61
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
77

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

Ben Adams
  • 3,281
  • 23
  • 26
  • 14
    It does; question is why can't you; answer is you can. – Ben Adams Oct 22 '16 at 00:26
  • 30
    It doesn't; question is why you can not reference an *existing variable*, already defined `ref` or `out`, inside a lambda. It is clear if you read the example code (try again to read it again). The accepted answer clearly explain why. Your answer is about using `ref` or `out` *parameter* to the lambda. Totally not answering the question and speaking about something else – edc65 Oct 22 '16 at 09:51
  • 6
    @edc65 is right ... this has nothing to do with the subject of the question, which is about the content of the lamba expression (on the right), not its parameter list (on the left). It's bizarre that this got 26 upvotes. – Jim Balter Dec 08 '16 at 03:13
  • 1
    But I still don't understand why it has been designed to be like this. Why do I have to explicitly define all types? Semantically I don't need to. Am I losing something? – joe Feb 20 '19 at 02:26
  • @joe I think that the reason is syntactical. – TN. Apr 08 '20 at 09:37
  • @JimBalter this answer is likely being upvoted because this question is #1 for search engines when looking for "c# lambda ref" – ShawnFeatherly May 08 '23 at 22:34
3

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();
}
nl-x
  • 11,762
  • 7
  • 33
  • 61
ticky
  • 363
  • 2
  • 12
1

You can not use an out parameter directly in a lambda expression. The reason why you can not do that is explained in the other answers.

Workaround

But you can use a local temporary variable with for the inner function and, after the inner function has been executed, assign the out value from the inner function to the out value of the outer function:
private static int OuterFunc (int i_param1, out int o_param2)
{
  int param2 = 0;
  var del    = () => InnerFunc (i_param1, out param2);
  int result = del ();

  o_param2 = param2;
  return result;
}

private static int InnerFunc (int i_param1, out int o_param2)
{
  o_param2 = i_param1;
  return i_param1;
}

private static void Main (string[] args)
{
  int result = OuterFunc (123, out int param2);
  Console.WriteLine (result);  // prints '123'
  Console.WriteLine (param2);  // prints '123'
}

Please note
The question was created in 2009. My answer was created in 2023 using C#10 and .NET 6. I don't know whether this answer had also worked back in 2009, which means, the code here might depend on enhancements to C# and .NET that might have been made in the meantime.

Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45
0

I will give you another example.

Description

The code below will throw out this error. Because the change brought by the lambda expression (i)=>{...} only works in the function test.

static void test(out System.Drawing.Image[] bitmaps)
{
    int count = 10;

    bitmaps = new System.Drawing.Image[count];
    Parallel.For(0, count, (i) =>
    {
        bitmaps[i] = System.Drawing.Image.FromFile("2.bmp");
    });
}

Solution

So, if you remove out of the parameter, it works.

static void test(System.Drawing.Image[] bitmaps)
{
    int count = 10;

    bitmaps = new System.Drawing.Image[count];
    Parallel.For(0, count, (i) =>
    {
        bitmaps[i] = System.Drawing.Image.FromFile("2.bmp");
    });
}

If you need out really, don't change the parameter in the lambda expression directly. Instead, use a temporary variable please.

static void test(out System.Drawing.Image[] bitmaps)
{
    int count = 10;

    System.Drawing.Image[] bitmapsTemp = new System.Drawing.Image[count];
    Parallel.For(0, count, (i) =>
    {
        bitmapsTemp[i] = System.Drawing.Image.FromFile("2.bmp");
    });
    bitmaps = bitmapsTemp;
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
笑先生
  • 31
  • 5