奇怪的C#编译器问题变量名模糊

Mik*_*sen 12 c#

我们来看下面的代码:

class Foo
{
   string bar;

   public void Method()
   {
      if (!String.IsNullOrEmpty(this.bar))
      {
         string bar = "Hello";
         Console.Write(bar);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

这将编译,一切都很好.但是,我们现在删除this.前缀:

class Foo
{
   string bar;

   public void Method()
   {
      if (!String.IsNullOrEmpty(bar)) // <-- Removed "this."
      {
         string bar = "Hello";
         Console.Write(bar);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我得到编译器错误.我同意这是一个错误,但是错误的位置让我感到困惑.错误发生在该行:

string bar = "Hello";
Run Code Online (Sandbox Code Playgroud)

随着消息:

名为'bar'的局部变量不能在此范围内声明,因为它会给'bar'赋予不同的含义,'bar'已在'父或当前'范围内用于表示其他内容

据我了解了关于编译器,声明bar悬挂在顶部Method()的方法.但是,如果是这样的话,行:

if (!String.IsNullOrEmpty(bar))
Run Code Online (Sandbox Code Playgroud)

应该被认为是不明确的,因为它bar可能是对实例字段或尚未声明的局部变量的引用.

对我来说,删除this.可能会导致另一行的编译错误似乎很奇怪.换句话说,声明局部bar变量是完全有效的,只要先前没有在范围内做出任何可能模糊的引用bar(注意,如果我注释掉,if (!String.IsNullOrEmpty(bar))那么错误就会消失).

这一切看起来都很迂腐,所以你的问题是什么?:

我的问题是为什么编译器在变量声明范围之前允许对变量进行模糊引用,然后将声明本身标记为冗余.如果没有歧义的引用barString.IsNullOrEmpty()是错误的更精确的位置?在我的例子中,它当然很容易被发现,但是当我在野外遇到这个问题时,引用就是页面并且更难以追踪.

Jon*_*eet 11

根据我对编译器的理解,bar的声明被提升到Method()方法的顶部.

不,事实并非如此.

错误消息在这里非常精确:

名为'bar'的局部变量不能在此范围内声明,因为它会给'bar'赋予不同的含义,'bar'已在'父或当前'范围内用于表示其他内容.

违反C#规范的部分是第7.6.2.1节(C#4和5规范):

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

在带注释的C#规范中,这有一个来自Eric Lippert的有用注释:

这条规则的一个更微妙的理想结果是,进行涉及绕地方变量声明的重构变得更加安全.任何会导致简单名称更改其语义的重构都将被编译器捕获.

除了其他任何事情,在我看来,这只是为了清晰.即使第二个版本被允许,第一个版本也是更清晰的IMO.编译器确保您不会编写病态不清楚的代码,当您可以非常轻松地修复它时,显而易见的是您的意思.

换句话说:你真的希望能够写第二个版本吗?

特别是:

在我的例子中,它当然很容易被发现,但是当我在野外遇到这个问题时,引用就是页面并且更难以追踪.

......这样可以让它合理吗?恰恰相反,我会说 - 你也应该把它视为重构你的代码的强烈鼓励,这样一个方法就不会"长篇".

  • 我实际上不明白为什么StackOverflow甚至*允许你对Jon Skeet的答案进行投票. (5认同)