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

C# 无法在lambda表达式中使用ref或out参数,c#,lambda,C#,Lambda,为什么不能在lambda表达式中使用ref或out参数 今天我遇到了这个错误并找到了解决方法,但我仍然很好奇为什么这是一个编译时错误 :无法在匿名方法、lambda表达式或查询表达式内使用in-ref或out参数'parameter' 下面是一个简单的例子: private void Foo() { int value; Bar(out value); } private void Bar(out int value) { value = 3; int[] ar

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

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

:无法在匿名方法、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;
}
这两个属性会产生一组特定的效果,这些效果会以以下方式出现在ref参数的前面

  • 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;
        };
}
公共静态扫描操作CreateScanOperation(
PrattTokenDefinition(标记定义)
{
var oldScanOperation=tokenDefinition.ScanOperation;//闭包仍然有效。
返回委托(字符串文本、ref int位置、ref位置信息currentPosition)
{
var标记=旧扫描操作(文本、参考位置、参考当前位置);
if(标记==null)
返回null;
if(tokenDefinition.leftEndotation!=null)
token.\u led=tokenDefinition.LeftEndotation(token);
if(tokenDefinition.nulldefination!=null)
token.\u nud=tokenDefinition.nulldefination(token);
token.Identifier=tokenDefinition.Identifier;
token.LeftBindingPower=tokenDefinition.LeftBindingPower;
token.OnInitialize();
返回令牌;
};
}

请记住,lambda在程序上和数学上都更安全(因为前面提到的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();
}

这是关于迭代器的,但这篇文章(也是Eric Lippert的,他毕竟是语言设计团队的成员)中的大部分推理也适用于lambdas:我可以问一下您找到了什么解决方法吗?您可以声明一个局部正态变量并使用它,然后将结果赋给值。。。添加一个var tempValue=value;现在可以找到@JoelCoehoorn所指的文章了,我想你误解了这个问题。问题是为什么lambda不能在其容器方法中访问ref/out变量,而不是为什么lambda本身不能包含ref/out变量。恐怕后者没有什么好的理由。今天我写了一个lambda
(a,b,c,ref d)=>{…}
,并且
ref
被红色下划线,错误消息是“参数'4'必须用'ref'关键字声明”。脸掌!什么是“ref值提升”?@Qwertie我用完全参数化来实现它,意思是,包括a、b、c和d上的类型,它可以工作。参见BenAdams的回答(尽管他也误解了最初的问题)。@Qwertie我想我只删除了这一点的一半——我认为最初的观点是将ref参数放入闭包可能有风险,但我必须随后意识到,在我给出的示例中没有发生这种情况(我也不知道这是否会被编译).这与实际提出的问题无关…请参阅已接受的答案以及Ben Adams在答案下的评论,他同样误解了问题。是的;问题是为什么你不能;答案是你可以。它不可以;问题是为什么你不能引用已定义的现有变量
ref
out
,在lambda中。如果您阅读示例代码,则会很清楚(请再次尝试阅读)。接受的答案清楚地解释了原因。您的答案是关于对lambda使用
ref
out
参数。完全不回答问题,也不谈论一些事情else@edc65是正确的…这与问题的主题无关,问题的主题是关于lamba表达式的内容(在右边),而不是其参数列表(在左侧).奇怪的是,这得到了26张赞成票。但我仍然不明白为什么它被设计成这样。为什么我必须明确定义所有类型?从语义上说,我不需要。我失去了什么吗?@joe我认为原因是句法上的。如果你仍然想使用它,那么你可以创建一个临时变量,并在lamda中使用它。有些像
int这样的东西
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();
}