C#变量范围:’x’不能在此范围内声明,因为它会给’x’赋予不同的含义

C# variable scoping: 'x' cannot be declared in this scope because it would give a different meaning to 'x'

1
2
3
4
5
6
if(true)
{
    string var ="VAR";
}

string var ="New VAR!";

这将导致:

Error 1 A local variable named 'var'
cannot be declared in this scope
because it would give a different
meaning to 'var', which is already
used in a 'child' scope to denote
something else.

真的没有什么惊天动地的事,但这不是很明显的错误吗?我和一个开发人员同事想知道第一个声明是否应该在不同的范围内,因此第二个声明不能干扰第一个声明。

为什么C无法区分这两个范围?第一个if范围是否应该与方法的其余部分完全分离?

我不能从IF外部调用var,所以错误消息是错误的,因为第一VAR在第二个范围内没有相关性。


这里的问题在很大程度上是一个良好的实践和防止无意中的错误。诚然,C编译器理论上可以设计成在这里范围之间没有冲突。然而,正如我所看到的,这将是一个事半功倍的努力。

假设父范围中var的声明在if语句之前,则存在无法解决的命名冲突。编译器只是不区分以下两种情况。分析是完全基于范围而不是声明/使用顺序来完成的,正如您所期望的那样。

理论上可以接受(但就C而言仍然无效):

1
2
3
4
5
6
if(true)
{
    string var ="VAR";
}

string var ="New VAR!";

以及不可接受的(因为它将隐藏父变量):

1
2
3
4
5
6
string var ="New VAR!";

if(true)
{
    string var ="VAR";
}

在变量和作用域方面都是完全相同的。

现在,为什么你不能给其中一个变量取一个不同的名字呢?我假设(希望)你的实际变量不是var,所以我不认为这是个问题。如果您仍然打算重用相同的变量名,只需将它们放在兄弟范围中:

1
2
3
4
5
6
7
8
if(true)
{
    string var ="VAR";
}

{
    string var ="New VAR!";
}

然而,尽管这对编译器有效,但在读取代码时可能会导致一定程度的混乱,所以我建议在几乎任何情况下都不要这样做。


isn't this just plain wrong?

不,这一点都不错。这是C规范第7.5.2.1节"简单名称、块中不变含义"的正确实现。

规范规定:

For each occurrence of a given
identifier as a simple-name in an
expression or declarator, within the
local variable declaration space
of that occurrence, every
other occurrence of the same
identifier as a simple-name in an
expression or declarator must refer to the same
entity. This rule ensures that the
meaning of a name is always the same
within a given block, switch block,
for-, foreach- or using-statement, or
anonymous function.

Why is C# unable to differentiate between the two scopes?

这个问题毫无意义;显然编译器能够区分这两个范围。如果编译器无法区分这两个作用域,那么如何产生错误?错误消息表示有两个不同的作用域,因此作用域已被区分!

Should the first IF scope not be completeley seperate from the rest of the method?

不,不应该。块语句在条件语句的结果中定义的作用域(和局部变量声明空间)在词汇上是外部块的一部分,外部块定义了方法体。因此,关于外块内容的规则适用于内块内容。

I cannot call var from outside the if,
so the error message is wrong, because
the first var has no relevance in the
second scope.

这是完全错误的。可以得出这样的结论:仅仅因为局部变量不再在作用域内,外部块就不包含错误。错误信息正确。

这里的错误与任何变量的作用域是否与任何其他变量的作用域重叠无关;这里唯一相关的是您有一个块(外部块),其中使用相同的简单名称来引用两个完全不同的东西。C要求简单名称在第一个使用它的块中具有一个含义。

例如:

1
2
3
4
5
6
7
8
class C
{
    int x;
    void M()
    {
        int x = 123;
    }
}

这是完全合法的;外部X的范围与内部X的范围重叠,但这不是一个错误。错误是:

1
2
3
4
5
6
7
8
9
10
11
12
class C
{
    int x;
    void M()
    {
        Console.WriteLine(x);
        if (whatever)
        {
            int x = 123;
        }
    }
}

因为现在简单的名称"x"在m的主体中意味着两个不同的东西——它意味着"this.x"和局部变量"x"。当同一个简单名称在同一块中意味着两个完全不同的东西时,开发人员和代码维护人员会感到困惑,因此这是非法的。

我们允许并行块包含两个不同的方式使用的相同的简单名称;这是合法的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class C
{
    int x;
    void M()
    {
        if (whatever)
        {
            Console.WriteLine(x);
        }
        if (somethingelse)
        {
            int x = 123;
        }
    }
}

因为现在包含X的两个不一致用法的唯一块是外块,并且该块不直接包含"X"的任何用法,只是间接地。


这在C++中是有效的,但是对于许多bug和不眠之夜来说是一个来源。我认为C的人认为最好是抛出一个警告/错误,因为在大多数情况下,它是一个bug,而不是编码人员真正想要的东西。

这里有一个有趣的讨论,讨论这个错误来自规范的哪些部分。

编辑(一些示例)-----

在C++中,以下是有效的(并且,如果外部声明在内部范围之前或之后并不重要,那么它将更有趣,并且如果以前是错误的话)。

1
2
3
4
5
6
7
8
9
void foo(int a)
{
    int count = 0;
    for(int i = 0; i < a; ++i)
    {
        int count *= i;
    }
    return count;
}

现在假设函数有几行长,可能很容易不发现错误。编译器从不抱怨(不是旧的,不确定C++的新版本),函数总是返回0。

行为显然是一个bug,所以如果C++或LIN程序或编译器指出这一点就好了。如果不是bug,只需重命名内部变量就可以轻松解决它。

为了增加对伤害的侮辱,我记得GCC和VS6对for循环中的计数器变量所属的位置有不同的看法。一个说它属于外部范围,另一个说它不属于。在跨平台代码上工作有点烦人。让我再举一个例子来说明我的行数。

1
2
3
4
5
6
7
8
for(int i = 0; i < 1000; ++i)
{
    if(array[i] > 100)
        break;
}

printf("The first very large value in the array exists at %d
"
, i);

此代码在VS6 IIRC中工作,而不是在GCC中工作。无论如何,C清理了一些东西,这很好。