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

时间:2015-04-20 14:26:29

标签: c# class lambda field 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()
    {
    }
}

以下是编译错误(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的报价答案)。 所谓的意味着什么是:

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

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

{
    int a;
}

{
    int a;
}

因为两个变量a的范围都不能被看到""来自其他人的范围;

并且不允许这样:

{
    int a;
}

int a;

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

并且不允许这样:

class Test
{
    int test;

    void method()
    {
        {
            int test;
        }

        test++;
    }
}

因为字段的增量可以被看到"从块的范围(它不是一个声明并不重要)。

似乎C#6改变了这个规则,特别是让最后一个例子(和我的原始代码)合法,尽管我并不真正理解其准确性。

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

3 个答案:

答案 0 :(得分:13)

Eric Lippert已撰写有关此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开始,事情发生了变化。这个规则已经消失了。有关详细信息,请参阅Jon's answer

答案 1 :(得分:9)

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

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

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

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

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

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

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

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

更新:规则已经过去了;有关详细信息,请参阅Jon的答案。

答案 2 :(得分:4)

我为此提出了Roslyn issue 2110 - C#6规范正在改变以允许这一点。 Mads Torgersen表示,改变是按设计进行的:

  

该规则很有意义,因为它应该以一种无声地改变其含义的方式将“移动代码”的风险降至最低。然而,我们谈过的每一个人似乎都只知道这个规则从何时引起他们不必要的悲伤 - 没有人因为禁令而得救。

     

此外,编译器以交互方式强制执行规则所需的额外跟踪导致代码的重大复杂性以及非平凡的性能开销。现在,如果这是一个很好的规则,我们无论如何都会保留它,但事实并非如此!所以它已经消失了。

在没有任何代理参与的情况下演示编译器版本之间的不一致很简单:

class Test
{
    static int test;

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

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

  

7.6.2.1块中的不变含义

     

对于给定标识符的每次出现,作为表达式或声明符中的完整简单名称(没有类型参数列表),在局部变量声明空间(第3.3节)内立即封闭该出现,每次出现时都相同标识符作为表达式或声明符中的完整简单名称必须引用同一实体。此规则确保在给定块,switch块,for-,foreach-或using-statement或匿名函数中名称的含义始终相同。

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

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

    {
        int x = 20;
    }
}

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

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

我会记下规范的不良措辞,并尝试将其修复为下一版ECMA规范。