为什么我可以声明一个与父级作用域中变量相同的子级变量?

27

最近我写了一些代码,不小心在一个函数内部的一个动作声明中把一个变量名称当做参数重复使用了,在这个函数里面已经有一个同名变量了。例如:

var x = 1;
Action<int> myAction = (x) => { Console.WriteLine(x); };

当我发现这个重复代码时,我很惊讶看到它能够编译和运行,而这不是我根据我对C#作用域的了解所期望的行为。一些快速的谷歌搜索显示出类似的代码产生错误,如Lambda Scope Clarification中所述的那样。 (我将该示例代码粘贴到我的IDE中以确保它可以运行; 它完美运行。) 此外,当我进入Visual Studio中的Rename对话框时,第一个x被突出显示为名称冲突。

为什么这段代码能够工作? 我使用的是带有Visual Studio 2019的C#8。


1
Lambda表达式被移动到编译器生成的类的方法中,因此该方法的整个“x”参数都被移出了作用域。请参见sharplab以获取示例。 - Lasse V. Karlsen
7
这里值得注意的是,如果目标是C#7.3,则此代码无法编译,因此这似乎只适用于C# 8。 - Jonathon Chase
链接问题中的代码在sharplab中也可以编译通过。这可能是最近的更改。 - Lasse V. Karlsen
2
发现了一个重复问题(没有答案):https://stackoverflow.com/questions/58639477/why-is-shadowing-of-locals-in-c-sharp-8-allowed - bolov
1个回答

30
这段代码为什么能够运行?我使用的是带有Visual Studio 2019的C# 8。
你已经回答了自己的问题!因为你使用的是C# 8。
从C# 1到7,规则是:在同一局部作用域中,简单名称不能用来表示两个不同的事物。(实际规则比这更复杂,但描述起来很繁琐;请参阅C#规范了解详情。)
这个规则的意图是防止你在示例中谈论的那种情况发生混淆局部含义的情况。特别地,该规则旨在防止类似以下混淆的情况:
class C 
{
  int x;
  void M()
  {
    x = 123;
    if (whatever)
    {
      int x = 356;
      ...

现在我们有一种情况,即在M的主体内,x既表示this.x,也表示局部的x
虽然出于善意,但这条规则存在许多问题:
  • 它没有按照规范实施。有些情况下可以使用简单名称,比如作为类型和属性,但是由于错误检测逻辑有缺陷,这些情况并不总是被标记为错误。(见下文)
  • 错误消息措辞令人困惑,并且报告不一致。对于这种情况,有多个不同的错误消息。它们没有一致地识别罪犯,即有时会指出内部使用,有时会指出外部使用,有时只是令人困惑。
我在Roslyn重写中做出了努力来解决这个问题;我添加了一些新的错误消息,并使旧的消息在报告错误位置方面保持一致。然而,这种努力太少,太晚了。
C#团队决定在C# 8中,整个规则引起的混乱比防止的混乱还要大,所以该规则已经从语言中删除。(感谢Jonathon Chase确定了退休时间。)
如果您有兴趣了解这个问题的历史以及我尝试如何解决它,请参阅我写的这些文章:

https://ericlippert.com/2009/11/02/simple-names-are-not-so-simple/

https://ericlippert.com/2009/11/05/simple-names-are-not-so-simple-part-two/

https://ericlippert.com/2014/09/25/confusing-errors-for-a-confusing-feature-part-one/

https://ericlippert.com/2014/09/29/confusing-errors-for-a-confusing-feature-part-two/

https://ericlippert.com/2014/10/03/confusing-errors-for-a-confusing-feature-part-three/

在第三部分的结尾,我指出这个功能与“颜色颜色”功能之间存在交互作用——即允许该功能的特征:{{Color Color}}。
class C
{
  Color Color { get; set; }
  void M()
  {
    Color = Color.Red;
  }
}

在这里,我们使用简单的名称Color来同时引用this.Color和枚举类型Color。按照规范的严格解释,这应该是一个错误,但在这种情况下,规范是错误的,意图是允许它,因为此代码是明确的,强制开发人员更改它将会很烦人。
我从未撰写过描述这两个规则之间所有奇怪交互的文章,现在这样做有点毫无意义!

问题中的代码在C# 6、7、7.1、7.2和7.3上无法编译,会出现“CS0136:由于已经存在一个名为'x'的局部变量或参数,因此无法在此范围内声明该名称...”的错误。看起来这个规则一直被执行到C# 8。 - Jonathon Chase
1
@JonathonChase:谢谢! - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接