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


173

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

它与迭代器有关,但本文中的大部分相同推理(毕竟也是埃里克·利珀特(Eric Lippert&mdash;他也是语言设计团队的一员))适用于lambda:< blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn

17
请问您找到了什么解决方法?
Beatles1692

3
您可以只声明一个局部普通变量并对其进行处理,然后将结果分配给value ...添加一个var tempValue = value; 然后使用tempValue。
醉酒代码猴子

Answers:


122

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表达式中不允许使用它们的原因之一。


36
我知道我们不能ref在lambda表达式内部使用,但是使用它的愿望并未得到满足。
zionpi

85

在后台,匿名方法是通过提升捕获的变量(这是您的问题主体所关心的)并将它们存储为编译器生成的类的字段来实现的。无法将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);
}

70

您可以,但必须明确定义 所有类型,因此

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

无效,但是

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

已验证


13
确实如此;问题是为什么你不能?答案是可以的。
本·亚当斯

24
没有 问题是为什么您不能在lambda中引用现有的已定义ref或的变量out。很明显,如果您阅读了示例代码(请重试以再次阅读)。接受的答案清楚地说明了原因。您的答案是关于lambda的使用refout 参数。完全不回答问题,而谈论其他东西
edc65

4
@ edc65是正确的……这与问题的主题无关,该问题与lamba表达式的内容(在右侧)有关,而不是其参数列表(在左侧)。奇怪的是,它有26个投票。
Jim Balter

6
它确实帮助了我。+1。谢谢
Emad

1
但是我仍然不明白为什么它被设计成这样。为什么必须显式定义所有类型?从语义上讲,我不需要。我在丢东西吗?

5

由于这是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值提升):您可能会打开一罐蠕虫。使用此语法时请仔细考虑。


3
我认为您误解了这个问题。问题是为什么lambda无法在其容器方法中访问ref / out变量,而不是为什么lambda本身不能包含ref / out变量。AFAIK没有充分的理由选择后者。今天,我写了一个lambda (a, b, c, ref d) => {...}ref并用红色下划线显示错误消息“必须使用'ref'关键字声明参数'4'”。Facepalm!PS什么是“参考价值提升”?
Qwertie

1
@Qwertie我使它可以与完整的参数化一起使用,这意味着包括a,b,c和d上的类型,并且可以正常工作。参见BenAdams的答案(尽管他也误解了最初的问题)。
Ed Bayiates

@Qwertie我想我只删除了这一点的一半-我认为最初的观点是将ref参数放入闭包可能有风险,但我必须随后意识到,在我给出的示例中并没有发生这种情况(并且也不做)我知道是否可以编译)。
乔纳森·迪金森

这与实际提出的问题无关……请参见本·亚当斯(Ben Adams)的回答以及接受的答案,后者同样误解了这个问题。
吉姆·巴尔特

1

也许这?

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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.