不能在 lambda 表达式中使用 ref 或 out 参数

Posted

技术标签:

【中文标题】不能在 lambda 表达式中使用 ref 或 out 参数【英文标题】:Cannot use ref or out parameter in lambda expressions 【发布时间】:2010-11-24 20:10:22 【问题描述】:

为什么?

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

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

这是一个简单的例子:

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

【问题讨论】:

这是关于迭代器的,但这篇文章中的大部分推理(同样由 Eric Lippert 撰写,毕竟他是语言设计团队的成员)适用于 lambda:blogs.msdn.com/ericlippert/archive/2009/07/13/…> 请问您找到的解决方法是什么? 您可以声明一个局部普通变量并使用它,然后将结果分配给 value... 添加一个 var tempValue = value;然后使用 tempValue。 文章@JoelCoehoorn的comment指的现在可以找到here 【参考方案1】:

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 参数可能具有固定的生命周期。考虑将局部变量作为 ref 参数传递给函数。 lambda 中的副作用需要在 ref 参数本身上可见。在方法中和调用者中。

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

【讨论】:

如果你还想使用它,那么你可以创建一个临时变量并在 lamda 中使用它。类似int tempVariable = refVariable; int newValue = array.Where(a =&gt; a == tempVariable).First();【参考方案2】:

在后台,匿名方法是通过提升捕获的变量(这是您的问题主体的全部内容)并将它们存储为编译器生成的类的字段来实现的。无法将refout 参数存储为字段。 Eric Lippert 在a blog entry 中讨论了它。请注意,捕获的变量和 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);

【讨论】:

【参考方案3】:

你可以,但你必须明确定义所有类型

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

但是无效

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

有效

【讨论】:

确实如此;问题是你为什么不能;答案是可以的。 它没有;问题是为什么您不能在 lambda 中引用已定义 refout现有变量。如果您阅读示例代码,则很清楚(再次尝试再次阅读)。接受的答案清楚地解释了原因。您的答案是关于使用 refout parameter 到 lambda。完全不回答问题并谈论其他事情 @edc65 是对的......这与问题的主题无关,这是关于lamba表达式的内容(在右边),而不是它的参数列表(在左边)。奇怪的是,这得到了 26 票。 但我还是不明白为什么它被设计成这样。为什么我必须明确定义所有类型?语义上我不需要。我失去了什么吗? @joe 我认为原因是语法上的。【参考方案4】:

因为这是 Google 上“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 在程序和数学上更安全(因为前面提到的 ref 值提升):您可能会打开一罐蠕虫。使用此语法时请仔细考虑。

【讨论】:

我认为您误解了这个问题。问题是为什么 lambda 不能在其容器方法中访问 ref/out 变量,而不是为什么 lambda 本身 不能包含 ref/out 变量。 AFAIK 后者没有充分的理由。今天我写了一个 lambda (a, b, c, ref d) =&gt; ...ref 是红色下划线的错误消息“参数'4'必须用'ref'关键字声明”。捂脸!附言什么是“参考价值提升”? @Qwertie 我得到这个可以完全参数化,意思是,包括 a、b、c 和 d 上的类型,它可以工作。请参阅 BenAdams 的回答(尽管他也误解了最初的问题)。 @Qwertie 我想我只删除了该点的一半 - 我认为最初的观点是将 ref 参数放入闭包中可能有风险,但我随后一定意识到这并没有发生在我给出的例子(我也不知道它是否会编译)。 这与实际提出的问题无关......请参阅 Ben Adams 的已接受答案和答案下的 cmets,他同样误解了这个问题。【参考方案5】:

也许这个?

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 表达式中使用 ref 或 out 参数的主要内容,如果未能解决你的问题,请参考以下文章

Lambda表达式

C++ Lambda 表达式:通过 ref 开销捕获

Lambda 表达式和变量捕获

匿名函数内置函数与模块

匿名函数-lambda

不能在 ref struct 中使用 yield 关键字?