Lambda参数在后面的范围内访问字段时与类字段冲突

Min*_*s97 21 c# lambda field class increment

在名称方面我的想象力很弱,所以我经常发现自己在代码中重用了标识符.这导致我遇到这个特定的问题.

这是一些示例代码:

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()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是编译错误(由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
Run Code Online (Sandbox Code Playgroud)

第11 test++行包含第9行包含lambda.

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

'test' conflicts with the declaration 'Namespace.Test.test'
Run Code Online (Sandbox Code Playgroud)

该错误仅在第11行的增量处发生.

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

这个问题让我感到惊讶 - 我确信lambda参数名称只能与本地方法变量名称冲突(当我注释掉增量时,代码编译会对此进行确认).另外,lambda参数如何可能影响增量,这正好在lambda的范围之外?

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

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

所以我想我终于理解了我破坏的规则.它在C#规范中没有措辞(7.6.2.1,参见Jon Skeet对报价的回答).这应该是什么意思:

如果其中一个违规用途(直接)位于可以从另一个范围"看到"的范围内,不能使用相同的标识符来引用同一"局部变量声明空间"中的不同事物(实体)是(直接)位于.

不是标准的标准措辞,但我希望你理解我的意思.这条规则应该允许这样:

{
    int a;
}

{
    int a;
}
Run Code Online (Sandbox Code Playgroud)

因为这两个变量的范围都不a能从另一个范围"看到";

并且不允许这样:

{
    int a;
}

int a;
Run Code Online (Sandbox Code Playgroud)

因为第二个变量声明是从第一个变量的范围"看到"的

并且不允许这样:

class Test
{
    int test;

    void method()
    {
        {
            int test;
        }

        test++;
    }
}
Run Code Online (Sandbox Code Playgroud)

因为可以从块的范围"看到"字段的增量(它不是声明无关紧要).

似乎C#6改变了这个规则,特别是使最后一个例子(和我的原始代码)合法,但我真的不明白究竟是怎么回事.

如果我在这些例子中犯了一些错误,请纠正我.

Sri*_*vel 13

Eric Lippert在博客上发表了关于这个简单名称并不是那么简单.

简单名称(没有完全限定名称)总是只代表一个代码块中的一个东西.如果违反它,将会出现CS0135编译器错误.

在你的方法中method,test是一个简单的名称,这意味着两件事.c#中不允许这样做.

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

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

    this.test++;
}
Run Code Online (Sandbox Code Playgroud)

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

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

    {
        test++;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,对于相同的简单名称,您没有两个不同的含义test.因为第二次测试住在不同的街区.

截至目前(2015年4月),这个答案是有效的.从C#6.0开始,事情发生了变化.这个规则已经消失了.有关详细信息,请参阅Jon的答案.


Eri*_*ert 9

不幸的是,Sriram Sakthivel的回答是正确的.C#有一个规则,我已经写了很多次,这要求在整个块中使用相同的简单名称具有相同的含义.

我同意错误信息非常混乱.我在罗斯林做了大量的工作,以确保这个错误信息在Roslyn中不那么混乱,这可能是徒劳的.

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

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

(从底部开始;这些是按时间顺序排列的.)

Jon和其他人正确地注意到在C#6的预发布版本中,您没有收到代码的错误.我相信在我离开团队之后,在这个错误条件下做了更多的工作,并且可能已经放宽了更宽松.

"一个名字必须只意味着一件事"的原则是一个好的原则,但实施起来很棘手,难以解释,并且在这个网站上有很多问题的来源,所以设计团队可能决定采用更容易解释和实施的规则.究竟是什么规则,我不知道; 我还没有时间浏览Roslyn源代码,看看代码的这一部分是如何随着时间的推移而演变的.

更新:我在Roslyn团队中的间谍告诉我它是23891e,这将大大缩短搜索范围.:-)

更新:规则已经过去了; 有关细节,请参阅Jon的答案.

  • "整个块"是否意味着包含嵌套块?如果是这样,通过在不同的嵌套块中声明两个局部变量来违反它是微不足道的.如果没有,很难看出它是如何被每个非嵌套块仅使用一次变量的代码所违反的. (2认同)
  • @JonSkeet:我同意措辞不清楚.目的是在"最高"的本地声明空间中名称必须是唯一的**直接*包含简单名称的用法.这是故意允许`{int x; } {int x; }和`for(int x ...){} for(int x ...){}`和`Where(x => y(x)).选择(x => z(x))` (2认同)