Lambda参数在后面的范围内访问字段时与类字段冲突
在名称方面我的想象力很弱,所以我经常发现自己在代码中重用了标识符。 这导致我遇到这个特定的问题。
这是一些示例代码:
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改变了这个规则,特别是使最后一个例子(和我的原始代码)合法,尽管我真的不明白究竟是怎么回事。
如果我在这些例子中犯了一些错误,请纠正我。
不幸的是,Sriram Sakthivel的回答是正确的。 C#有一个规则,我已经写了很多次,这要求在整个块中使用相同的简单名称具有相同的含义。
我同意错误消息非常混乱。 我在Roslyn做了大量的工作,以确保这个错误信息在Roslyn中不那么令人困惑,这可能是徒劳的。
您可以阅读我关于此规则的文章,以及我为改进错误消息所做的工作,此处:
http://ericlippert.com/tag/simple-names/
(从底部开始;这些是按时间顺序排列的。)
Jon和其他人正确地指出,在C#6的预发布版本中,您没有收到代码的错误。 我相信在我离开团队之后,在这个错误条件下做了更多的工作,并且可能已经放宽了更宽松。
“一个名字必须只意味着一件事”的原则是一个好的原则,但实施起来很棘手,难以解释,而且这个网站上有很多问题的来源,所以设计团队可能决定采用更容易解释和实施的规则。 究竟是什么规则,我不知道; 我还没有时间浏览Roslyn源代码,看看代码的这一部分是如何随着时间的推移而演变的。
更新:我在Roslyn团队的间谍告诉我它是23891e,这将大大缩短搜索范围。 🙂
更新:规则已经过去了; 详见Jon的回答。
Eric Lippert在博客上发表了关于这个简单名称并非如此简单 。
简单名称(没有完全限定名称)总是意味着代码块中只有一个东西。 如果违反它,将会出现CS0135编译器错误 。
在您的方法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的答案 。
我为此提出了Roslyn问题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节)内立即包围该出现,每次出现的同一标识符为a表达式或声明符中的完整简单名称必须引用同一实体。 此规则确保名称的含义在给定块,switch块,for-,foreach-或using-statement或匿名函数中始终相同。
就个人而言,我认为这是一个不太理想的规范。 目前尚不清楚“在给定块内”是否包含嵌套块。 例如,这很好:
static void Main() { { int x = 10; } { int x = 20; } }
…尽管x
用于引用Main
方法的“顶级”块中的不同变量,但是如果包含嵌套。 因此,如果您考虑该块,它违反了“此规则确保名称的含义在给定块[…]内始终相同”的说法“但是,我认为不会检查块,因为它不是任何使用x
的“立即封闭”变量声明空间。
因此在我看来,错误与规范的第一个引用部分一致,但与最终句子不一致,后者是ECMA规范中的注释。
我将记录该规范的不良措辞,并尝试将其修复为下一版本的ECMA规范。