Lambda 参数与访问后续范围内字段的 class 字段冲突

Lambda parameter conflicting with class field on accessing field in later scope

我在命名方面缺乏想象力,所以我经常发现自己在代码中重复使用标识符。这导致我 运行 陷入这个特定问题。

下面是一些示例代码:

public delegate void TestDelegate(int test);

public class Test
{
    private int test;

    private void method(int aaa)
    {
        TestDelegate del = test => aaa++;

        test++;
    }

    public static void Main()
    {
    }
}

以下是编译错误(由ideone输出):

prog.cs(11,3): error CS0135: `test' conflicts with a declaration in a child block
prog.cs(9,22): (Location of the symbol related to previous error)
Compilation failed: 1 error(s), 0 warnings

第 11 行包含 test++,第 9 行包含 lambda。

顺便说一句,Visual Studio 2013 给出了不同的错误:

'test' conflicts with the declaration 'Namespace.Test.test'

错误仅出现在第11行的增量处。

如果我注释掉第 9 行(lambda)或第 11 行(增量),代码编译成功。

这个问题让我很吃惊——我确信 lambda 参数名称只能与本地方法变量名称冲突(当我注释掉增量时,编译代码证实了这一点)。另外,lambda 参数怎么可能影响恰好在 lambda 范围之外的增量?

我无法理解这个……我究竟做错了什么?在这种情况下,神秘的错误消息是什么意思?

在所有伟大的答案之后编辑:

所以我想我终于明白了我打破的规则。它在 C# 规范中措辞不当(7.6.2.1,请参阅 Jon Skeet 对引用的回答)。 supposed 的意思是:

如果其中一个违规用途是(直接)位于一个范围内,该范围可以 "seen" 来自另一个(直接)所在的范围 .

不是标准的标准措辞,但我希望你明白我的意思。这条规则应该允许:

{
    int a;
}

{
    int a;
}

因为两个变量a的作用域都不能"seen"来自对方的作用域;

并禁止这样做:

{
    int a;
}

int a;

因为第二个变量声明来自第一个变量的范围"seen"

并禁止这样做:

class Test
{
    int test;

    void method()
    {
        {
            int test;
        }

        test++;
    }
}

因为字段的增量可以是块范围内的"seen"(不是声明无关紧要)。

似乎 C#6 改变了这个规则,特别是使最后一个例子(和我的原始代码)合法,虽然我不太明白到底是怎么回事。

如果我在这些例子中有错误,请指正。

Eric Lippert has blogged about this Simple names are not so simple.

简单名称(没有完全限定名称)在一段代码中总是只表示一个意思。如果你违反它会有一个 CS0135 compiler error.

在你的方法method中,test是一个简单的名字,有两个意思。在 c# 中是不允许的。

如果您使测试字段使用限定名称而不是简单名称,编译器错误将消失。

private void method(int aaa)
{
    TestDelegate del = test => aaa++;

    this.test++;
}

或者,如果您在不同的块中访问测试字段,编译器将很乐意编译。

private void method(int aaa)
{
    TestDelegate del = (int test) => aaa++;

    {
        test++;
    }
}

现在您不会对同一个简单名称有两种不同的含义 test。因为第二个测试住在不同的街区。

截至目前(2015 年 4 月)此答案有效。从 C#6.0 开始,情况发生了变化。这条规则已经消失了。详情请参阅

我为此提出了 Roslyn issue 2110 - C# 6 规范正在更改以允许这样做。 Mads Torgersen 表示更改是设计使然:

The rule was well intended, in that it was supposed to minimize the risk of "moving code around" in a way that would silently change its meaning. However, everyone we talked to only seemed to know about the rule from when it was causing them unnecessary grief - no-one had ever been saved by its prohibitions.

Furthermore, the extra tracking necessary in the compiler to enforce the rule interactively was causing significant complications to the code, as well as non-trivial performance overhead. Now, if it were a good rule we would have kept it anyway, but it isn't! So it's gone.

在不涉及任何委托的情况下演示编译器版本之间的不一致很简单:

class Test
{
    static int test;

    static void Main()
    {
        {
            int test = 10;
        }
        test++;
    }
}

C# 5 规范的相关部分是 7.6.2.1,它给出了这个规则:

7.6.2.1 Invariant meaning in blocks

For each occurrence of a given identifier as a full simple-name (without a type argument list) in an expression or declarator, within the local variable declaration space (§3.3) immediately enclosing that occurrence, every other occurrence of the same identifier as a full simple-name in an expression or declarator must refer to the same entity. This rule ensures that the meaning of a name is always the same within a given block, switch block, for-, foreach- or using-statement, or anonymous function.

就我个人而言,我认为这是一个不太理想的规范。目前尚不清楚“在给定块内”是否意味着包含嵌套块。例如,这很好:

static void Main()
{
    {
        int x = 10;
    }
    
    {
        int x = 20;
    }
}

... 尽管 x 用于引用 Main 方法的“顶级”块中的不同变量,但如果包含嵌套。因此,如果您考虑该块,它违反了“此规则确保名称的含义在给定块中始终相同 [...]”的说法。但是,我相信不会为此检查该块,因为它不是任何使用 x.

的“立即封闭”变量声明 space

所以在我看来,错误与规范的第一个引用部分一致,但与最后一句话一致,这是 ECMA 中的注释规格

我会记下该规范的糟糕措辞,并尝试在下一版本的 ECMA 规范中修复它。

Sriram Sakthivel 不幸被否决的答案是正确的。 C# 有一个规则,我已经写过很多次了,它要求在整个块中每次使用相同的简单名称都具有相同的含义。

我同意错误消息非常混乱。我在 Roslyn 中做了大量工作以确保此错误消息在 Roslyn 中不那么令人困惑,这些工作可能是徒劳的。

您可以在此处阅读我关于此规则的文章,以及我为改进错误消息所做的工作:

http://ericlippert.com/tag/simple-names/

(从底部开始;这些按时间倒序排列。)

Jon 和其他人正确地注意到,在 C# 6 的预发布版本中,您的代码不会出现错误。我相信在我离开团队之后,针对这个错误情况做了更多的工作,并且可能已经放松得更宽容了。

"one name must mean only one thing"的原理很好,但是实现起来很棘手,解释起来很棘手,而且这个网站上有很多问题的来源,所以设计团队可能决定采用更易于解释和实施的规则。我不知道那条规则到底是什么;我还没有时间浏览 Roslyn 源代码,看看这部分代码是如何随着时间的推移而演变的。

更新:我在 Roslyn 团队的间谍告诉我这是提交 23891e,这将大大缩短搜索时间。 :-)

更新:该规则已永久失效;有关详细信息,请参阅 Jon 的回答。