为什么不能在lambda表达式中使用ref或out参数?

我今天遇到了这个错误,并找到了一个解决方案,但我仍然好奇为什么这是一个编译时错误。

CS1628:不能在匿名方法、lambda表达式或查询表达式中使用in ref或out参数'parameter'

这里有一个简单的例子:

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();
}

当前回答

我再举一个例子。

描述

下面的代码将抛出这个错误。因为lambda表达式(i)=>{…}只在函数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");
    });
}

解决方案

如果去掉参数,就可以了。

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");
    });
}

如果真的需要,不要直接更改lambda表达式中的参数。相反,请使用临时变量。

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;
}

其他回答

也许这个呢?

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();
}

在底层,匿名方法是通过提升捕获的变量(这就是问题主体的全部内容)并将它们存储为编译器生成的类的字段来实现的。没有办法将ref或out形参存储为字段。埃里克·利珀特在一篇博客文章中对此进行了讨论。注意,捕获的变量和lambda参数之间是有区别的。你可以像下面这样使用“形式参数”,因为它们不是被捕获的变量:

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);
}

lambda的外观是改变它们捕获的变量的生命周期。例如,下面的lambda表达式使参数p1比当前方法帧存在的时间更长,因为它的值可以在方法帧不再在堆栈上之后被访问

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

捕获变量的另一个属性是,对变量的更改在lambda表达式之外也是可见的。例如,下面的代码输出42

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

这两个属性会以以下方式在ref参数面前产生特定的效果:

Ref参数可能有固定的生命周期。考虑将局部变量作为引用参数传递给函数。 lambda中的副作用需要在ref参数本身上可见。在方法内部和调用方内部。

这些属性有些不兼容,也是lambda表达式中不允许使用它们的原因之一。

我再举一个例子。

描述

下面的代码将抛出这个错误。因为lambda表达式(i)=>{…}只在函数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");
    });
}

解决方案

如果去掉参数,就可以了。

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");
    });
}

如果真的需要,不要直接更改lambda表达式中的参数。相反,请使用临时变量。

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;
}

可以,但必须显式地定义所有类型

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

无效,但是

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

是有效的