为什么不能在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();
}

在底层,匿名方法是通过提升捕获的变量(这就是问题主体的全部内容)并将它们存储为编译器生成的类的字段来实现的。没有办法将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表达式中不允许使用它们的原因之一。


因为这是“c# lambda ref”在谷歌上的顶级结果之一;我觉得我有必要对上述答案进行进一步阐述。旧的(c# 2.0)匿名委托语法是有效的,它确实支持更复杂的签名(以及闭包)。Lambda和匿名委托至少在编译器后端共享可感知的实现(如果它们不相同的话)——最重要的是,它们支持闭包。

当我做搜索时,我试图做的是演示语法:

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

只要记住Lambdas在程序和数学上更安全(因为前面提到的ref值提升):您可能会打开一个棘手的问题。使用此语法时请仔细考虑。


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

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

无效,但是

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

是有效的


也许这个呢?

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

我再举一个例子。

描述

下面的代码将抛出这个错误。因为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;
}

不能在lambda表达式中直接使用out形参。你不能那样做的原因在其他答案中有解释。

解决方案

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

请注意 这个问题创建于2009年。我的答案是在2023年用c# 10和。net 6创建的。我不知道这个答案在2009年是否也适用,这意味着,这里的代码可能依赖于在此期间对c#和。net的增强。