12

让我们看下面的代码:

class Foo
{
   string bar;

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

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

class Foo
{
   string bar;

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

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

string bar = "Hello";

随着消息:

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

根据我对编译器的了解,声明bar提升Method()方法的顶部。但是,如果是这种情况,则该行:

if (!String.IsNullOrEmpty(bar))

应该被认为是模棱两可的,因为bar可能是对实例字段或尚未声明的局部变量的引用。

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

这一切似乎都很迂腐,所以你的问题是什么?:

我的问题是为什么编译器允许在范围内声明变量之前对变量进行模棱两可的引用,但随后将声明本身标记为冗余bar对in的模棱两可的引用不应该String.IsNullOrEmpty()是错误的更精确位置吗?在我的示例中,它当然很容易发现,但是当我在野外遇到这个问题时,参考是向上的页面并且更难追踪。

4

2 回答 2

11

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

不,事实并非如此。

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

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

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

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

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

该规则的一个更微妙的理想结果是,进行涉及移动局部变量声明的重构变得更安全。任何会导致简单名称更改其语义的重构都将被编译器捕获。

但除此之外,在我看来,这只是为了清楚起见。即使第二个版本被允许,第一个更清晰的 IMO。编译器确保您不会编写病态不清楚的代码,当您可以很容易地修复它以明确您的意思时。

换句话说:你真的想写第二个版本吗?

尤其:

在我的示例中,它当然很容易发现,但是当我在野外遇到这个问题时,参考是向上的页面并且更难追踪。

...这使得允许它更合理?恰恰相反,我会说 - 您还应该将其视为强烈鼓励重构您的代码,以便单个方法不会“页面”长。

于 2012-11-27T18:25:21.040 回答
1

bar 的第二个定义没有拉到方法级别,它的作用域是 if 块。例如,这是完全有效的。

class Foo
{
    private string bar;

    public void Method()
    {
        if (!String.IsNullOrEmpty(bar)) // <-- No more this.
        {
            string bar1 = "Hello";
            Console.Write(bar);
        }

        if (!String.IsNullOrEmpty(bar)) // <-- No more this.
        {
            string bar1 = "Hello";
            Console.Write(bar);
        }
    }
}

原因是bar不再模棱两可,在bar/的外部和内部范围之间有明确的名称区别 - 编译器不允许通过本地定义bar1覆盖外部范围。bar

于 2012-11-27T18:26:17.893 回答