无法在lambda表达式中使用ref或out参数。

212

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

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

CS1628: 在匿名方法、lambda 表达式或查询表达式中无法使用 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表达式。Eric Lippert(他是语言设计团队的一员)在这篇文章中也提到了类似的观点。你可以在这个链接中找到相关内容:http://blogs.msdn.com/ericlippert/archive/2009/07/13/iterator-blocks-part-two-why-no-ref-or-out-parameters.aspx - Joel Coehoorn
20
请问您找到了什么解决方法? - Beatles1692
4
你可以声明一个本地普通变量并使用它,然后将结果赋值给value变量。添加一个"var tempValue = value;",然后使用tempValue即可。 - Drunken Code Monkey
@JoelCoehoorn的评论所提到的文章现在可以在这里找到。 - Bill Tür stands with Ukraine
1
@Beatles1692:这是我找到的解决方法:https://dev59.com/wXM_5IYBdhLWcg3wdy_z#75362003 - Tobias Knauss
7个回答

141

Lambdas给人的印象是改变了它们捕获的变量的生命周期。例如,以下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参数可能具有固定的生命周期。考虑将局部变量作为ref参数传递给函数。
  • lambda中的副作用需要在方法内和调用方中都可见于ref参数本身。

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


4
如果您仍想使用它,那么您可以创建一个临时变量,并在lambda函数内使用它。类似于 int tempVariable = refVariable; int newValue = array.Where(a => a == tempVariable).First(); - sree

92

匿名方法在内部实现时,通过提升捕获变量(这就是你的问题所涉及的内容),并将它们作为编译器生成类的字段存储。无法将refout参数存储为字段。Eric Lippert在一篇博客中讨论了这个问题。请注意,在捕获变量和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);
}

78

你可以这样做,但必须明确地定义所有类型。

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

是无效的,然而

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

有效


14
可以的;问题是为什么你不能;答案是你可以。 - Ben Adams
30
为什么在 lambda 表达式中不能引用已经定义的“ref”或“out”变量?这并不影响结果;如果您仔细阅读示例代码,就会明白原因(请再试着读一遍)。接受的答案已经很清楚地解释了为什么不能这样做。您的回答是关于在 lambda 表达式中使用“ref”或“out”参数,完全没有回答问题,而是谈论其他事情。 - edc65
6
@edc65是正确的......这与问题的主题无关,问题是关于lambda表达式的内容(右侧),而不是它的参数列表(左侧)。这个回答得到26个赞实在是很奇怪。 - Jim Balter
1
但我仍然不明白为什么它被设计成这样。为什么我必须明确定义所有类型?从语义上讲,我并不需要这样做。我失去了什么吗? - joe
@joe 因为 ref 修饰的是类型,而不是变量;ref d 没有任何意义,但 ref int 有。虽然他们可以为了方便而修改语法以允许 ref d,但他们选择不这样做。至于为什么不允许 (a, b, c, ref int d) ... 谁知道呢?提交一个关于语言改变的提案吧。 - undefined
显示剩余3条评论

5
作为谷歌搜索“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;
        };
}

请记住,Lambda表达式在程序和数学上更加安全(因为前面提到的引用值推广):你可能会惹上麻烦。在使用这种语法时,请仔细考虑。


3
我认为你误解了问题。问题是为什么一个 lambda 无法访问其容器方法中的 ref/out 变量,而不是为什么_lambda_本身不能包含 ref/out 变量。据我所知,后者没有充分的理由。今天我写了一个 lambda (a, b, c, ref d) => {...},并且 ref 被标记成红色下划线,提示错误消息“第4个参数必须使用 ref 关键字声明”。面掌!P.S. “ref 值提升”是什么? - Qwertie
1
@Qwertie 我已经成功地实现了完全参数化,也就是说,在 a、b、c 和 d 上包括类型,并且它可以工作。请参考BenAdams的答案(尽管他也误解了原始问题)。 - Ed Bayiates
@Qwertie 我想我只删除了那个观点的一半 - 我认为最初的观点是将ref参数放入闭包可能存在风险,但随后我必须意识到在我给出的示例中并没有发生这种情况(也不知道是否会编译)。 - Jonathan Dickinson
这与实际提出的问题无关...请查看Ben Adams的被接受的答案和答案下面的评论,他同样误解了问题。 - Jim Balter

3
也许这个呢?
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();
}

1

在lambda表达式中,你不能直接使用输出参数。为什么不能这样做已经在其他答案中得到了解释。

解决方法

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年创建的。我的答案是使用C#10和.NET 6在2023年创建的。我不知道这个答案是否也适用于2009年,这意味着这里的代码可能依赖于在此期间可能进行的C#和.NET增强功能。


0

我会给你另一个例子。

描述

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

解决方案

因此,如果将参数中的out删除,则可以正常工作。

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

如果你真的需要 out,请不要直接更改 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接