【问题标题】:C# variable scoping is not consistentC# 变量范围不一致
【发布时间】:2012-03-08 23:47:53
【问题描述】:

C# 在变量作用域方面相当挑剔。它怎么可能接受下面的代码?

class Program
{
    int x = 0;

    void foo()
    {
        int x = 0;
        x = 1;
        
        Console.WriteLine(x);
    }
}

如果你问我,那显然是命名冲突。编译器 (Visual Studio 2010) 仍然接受它。为什么?

【问题讨论】:

  • 你可以参考xthis.x,所以没有问题。
  • 其实是一致的。如果您在 void foo() 函数中只有 x=0,那么它将从您之前定义的 x 中获取变量。因为您在方法中有 int x = 0 ,所以您为所有意图实例化了一个新变量。但是如果你在 foo 方法之外查看 x 的值,它仍然是 0,因为你在不同的范围内。
  • 如果你不喜欢有一个与类成员同名的局部变量,那么你可以得到 ReSharper 和have it prevent you from doing this
  • 是的,从您的角度来看,这是命名冲突,但这是语言设计者预先确定的解决方案;局部变量优先于实例变量。编译器接受它的原因是因为它在语言定义中是有效的。我怀疑定义优先级的原因是决定如何处理这种情况相当容易和直观。对于其他情况(具有重叠范围的两个局部变量等),情况会更小,并被标记为错误。
  • 您可能还会发现Scope vs Declaration Space vs Lifetime 很有趣。

标签: c# scope


【解决方案1】:

C# 名称隐藏的规则相当复杂。该语言允许您提到的情况,但不允许许多类似情况。见

Simple names are not so simple, part one

关于这个复杂主题的一些信息。

解决您的具体问题:编译器当然可以检测到该冲突。事实上,它确实检测到这种冲突:

class P
{
    int x;
    void M()
    {
        x = 123; // The author intends "this.x = 123;"
        int x = 0;
    }
}

等效的 C++ 程序将是合法的 C++,因为在 C++ 中,局部变量在其声明点进入范围。在 C# 中,局部变量在其整个块的范围内,并且在声明之前使用它是非法的。如果你尝试编译这个程序,你会得到:

error CS0844: Cannot use local variable 'x' before it is declared.
The declaration of the local variable hides the field 'P.x'.

请参阅:本地声明隐藏字段。编译器知道这一点。那么为什么在你的情况下隐藏字段不是错误

让我们假设它应该是一个错误。这也应该是一个错误吗?

class B
{
    protected int x;
}

class D : B
{
    void M()
    {
        int x;
    }
}

字段x是从B继承而来的D的成员。所以这也应该是一个错误,对吧?

现在假设你有这个由 Foo Corporation 制作的程序:

class B
{
}

还有这个由 Bar Corporation 制作的程序:

class D : B
{
    void M()
    {
        int x;
    }
}

编译。现在假设 Foo Corp 更新了他们的基类并发布了一个新版本给你:

class B
{
    protected int x;
}

您是在告诉我每个包含名为 x 的局部变量的派生类现在都应该无法编译?

那太可怕了。我们必须允许局部变量影响成员。

如果我们要允许本地人影子基类的成员,那么不允许本地人影子类的成员似乎非常奇怪。

【讨论】:

  • 这让我很困惑。由于 C# 不允许 C/C++ 允许的许多命名冲突,因此我希望上述场景也有这种严格性。
  • @NOPslider:我添加了一些文字来解释为什么这是一个坏主意。
【解决方案2】:

C# 4.0 规范说明了通过嵌套隐藏作用域:

3.7.1.1 通过嵌套隐藏

通过嵌套隐藏名称可能是在命名空间内嵌套命名空间或类型的结果,作为 在类或结构中嵌套类型的结果,以及由于 参数和局部变量声明。在示例中

class A {
    int i = 0;

    void F() {
        int i = 1;
    }

    void G() {
        i = 1;
    }
}

在F方法中,实例变量i被局部隐藏 变量 i,但在 G 方法中,i 仍然指的是实例 变量。

当内部范围内的名称隐藏外部范围内的名称时 范围,它隐藏了该名称的所有重载。

在示例中

class Outer {
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        void G() {
            F(1);           // Invokes Outer.Inner.F
            F("Hello");     // Error
        }

        static void F(long l) {}
    }
}

调用 F(1) 调用在 Inner 中声明的 F,因为所有外部 F 的出现被内部声明隐藏。对于相同的 因此,调用 F("Hello") 会导致编译时错误。

【讨论】:

    【解决方案3】:

    这很正常。

    在构造函数中我经常使用相同的。

    public Person(string name) {
      this.name = name;
    }
    

    否则将无法声明命名为成员变量的方法参数。

    【讨论】:

      【解决方案4】:

      这不是模棱两可的。本地将是假定在您的函数中引用的变量。如果需要获取类变量,this.x 允许名称解析。

      【讨论】:

        【解决方案5】:

        没有命名冲突。编译器总是采用nearest/least 范围变量。

        在这种情况下,这就是您在 foo 中声明的 x 变量。 每个变量都可以以特定的方式访问,因此没有任何命名冲突。

        如果你想访问外部的 x,你可以使用this.x

        【讨论】:

        • 是“最近的”字面意思吗?
        【解决方案6】:

        因为规则是,如果局部变量和类成员之间存在冲突,则局部变量具有更高的优先级。

        【讨论】:

          【解决方案7】:

          这不是命名冲突:在 C# 中,局部变量优先于同名的实例变量,因为它们的 scope 更窄。

          当编译器将名称引用匹配到名称声明时,它会使用范围最窄的匹配声明

          有关该主题的详细信息,请参阅Reference Matching 上的文档。

          【讨论】:

            猜你喜欢
            • 2017-10-30
            • 2014-02-13
            • 1970-01-01
            • 2021-11-14
            • 2016-06-21
            • 2011-12-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多