【问题标题】:C# variable scoping: 'x' cannot be declared in this scope because it would give a different meaning to 'x'C# 变量作用域:不能在此范围内声明“x”,因为它会给“x”赋予不同的含义
【发布时间】:2011-01-04 04:39:51
【问题描述】:
if(true)
{
    string var = "VAR";
}

string var = "New VAR!";

这将导致:

错误 1 ​​名为 'var' 的局部变量 不能在此范围内声明 因为它会给你一个不同的 意思是'var',这已经是 在“子”范围内用于表示 别的东西。

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

为什么 C# 无法区分这两个作用域?第一个 IF 范围不应该与方法的其余部分完全分开吗?

我不能从 if 之外调用 var,所以报错信息是错误的,因为第一个 var 在第二个作用域中没有相关性。

【问题讨论】:

  • 实际的编译器错误是 - 不能在此范围内声明名为“x”的局部变量,因为它会给“x”赋予不同的含义,它已在“父级或当前”中使用范围来表示其他东西。
  • 我认为您使用的是 C# 2.0,因为 var 是 C# 3.0 中的关键字。
  • @IceHeat - 看起来你陷入了假设 C# 比实际更像 C++ 的陷阱。尽量不要从 C++ 所做的事情与 C# 所做的事情进行推理——那是危险的领域!
  • var 不是 C# 3.0 的 reserved 关键字。 "var" 仅在 (1) 它作为局部变量声明的类型出现(或使用块 decl、或 foreach 或 for 等)和 (2) 当没有一个名为“var”的类型已经在范围内。自 C# 1 以来,C# 从未添加过保留关键字;所有的新关键字都是 contextual 关键字。它们仅在赋予它们含义的上下文中用作关键字时才具有含义;否则它们是合法的标识符。

标签: c# scope


【解决方案1】:

这里的问题主要是一种良好的做法和防止无意的错误。诚然,C# 编译器理论上可以设计成在此范围之间不存在冲突。然而,在我看来,这将是很多努力却收效甚微。

考虑如果var 在父作用域中的声明在if 语句之前,那么将存在无法解决的命名冲突。编译器根本不区分以下两种情况。分析是完全基于范围完成的,而不是您似乎期望的声明/使用顺序。

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

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

string var = "New VAR!";

和不可接受的(因为它会隐藏父变量):

string var = "New VAR!";

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

在变量和范围方面都完全相同。

现在,在这种情况下,您是否有任何实际原因不能只为其中一个变量指定不同的名称?我假设(希望)你的实际变量没有被称为var,所以我真的不认为这是一个问题。如果您仍然打算重用相同的变量名,只需将它们放在同级作用域中:

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

{
    string var = "New VAR!";
}

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

【讨论】:

  • 当然编译器可以区分这两个作用域,内部变量只会隐藏外部,使其无法访问。
  • 是的,正是因为它会导致混乱/被认为是糟糕的设计实践,所以不允许这样做。
  • 从 IL 的角度来看很好 我很确定 if{} 范围不存在,所以这意味着让编译器为潜在混淆的价值做一些技巧,所以如果我正确,它不是主动的不允许但被动地。所需的功能根本没有实现
  • 顺便说一句,您在对已删除帖子的评论中说条件语句创建了一个范围。当然不是。 block 创建一个范围。许多语句确实创建了作用域——for、foreach、using、try/catch/finally 等等。但是,条件语句不是其中之一;创建的范围是由块创建的,而不是由条件创建的。
  • @Eric:相信你会指出类似的事情。 :) 当然,您是对的,尽管这是一个相当微妙的观点。实际上,大括号表示一个块,除了您指出的明显例外,它表示一个块。如果我错了,请纠正我,但如果具有单行操作的语句不包含子范围?
【解决方案2】:

这不是完全错误的吗?

不,这根本没有错。这是 C# 规范第 7.5.2.1 节“简单名称,块中的不变含义”的正确实现。

规范规定:


对于给定的每次出现 标识符作为简单名称 表达式或声明符,在 局部变量声明空间 那次发生的每一个 其他相同的情况 标识符作为简单名称 表达式或声明符必须引用相同的 实体。该规则确保 名字的意思总是一样的 在给定的块内,开关块, for-、foreach- 或 using- 语句,或 匿名函数。


为什么 C# 无法区分这两个作用域?

这个问题很荒谬;显然编译器 能够区分这两个作用域。如果编译器无法区分这两个作用域,那么如何产生错误?错误信息有两个不同的作用域,因此作用域已经区分了!

第一个 IF 范围不应该与方法的其余部分完全分开吗?

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

我不能从 if 之外调用 var, 所以错误信息是错误的,因为 第一个 var 在 第二个作用域。

这是完全错误的。仅仅因为局部变量不再在范围内,外部块不包含错误的结论是似是而非的。错误信息是正确的。

这里的错误与任何变量的范围是否与任何其他变量的范围重叠无关;这里唯一相关的是你有一个块 - 外部块 - 在其中使用相同的简单名称来指代两个完全不同的事物。 C# 要求一个简单的名称​​在首先使用它的整个块中都有一个含义

例如:

class C 
{
    int x;
    void M()
    { 
        int x = 123;
    }
}

这是完全合法的;外部 x 的范围与内部 x 的范围重叠,但这不是错误。什么是错误是:

class C 
{
    int x;
    void M()
    { 
        Console.WriteLine(x);
        if (whatever)
        {
            int x = 123;
        }
    }
}

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

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

class C 
{
    int x;
    void M()
    { 
        if (whatever)
        {
            Console.WriteLine(x);
        }
        if (somethingelse)
        {
            int x = 123;
        }
    }
}

因为现在唯一包含 x 的两个不一致用法的块是外部块,并且 该块不直接包含任何“x”的用法,只是间接地 .

【讨论】:

  • @Eric:我觉得blogs.msdn.com/ericlippert/archive/2009/08/03/… 在这里很重要。
  • 好点;尽管实际上我们是将块作为局部变量声明空间还是将块作为局部变量的范围来讨论的问题与该问题几乎无关。问题似乎与声明空间有关,因为声明已被标记。但实际上它是关于 scope 的——我们有两个实体,它们都通过它们的非限定名称成功查找,并绑定到同一块中的不同实体。
  • 从同一个块中访问同名的字段和局部变量的例子可能是这个设计决策的基本原理最有启发性的例子。
  • 可能只有我一个人,但你回答的整体语气似乎有点咄咄逼人。
  • @PhilipWallace:您可能有兴趣阅读我关于该主题的文章:blogs.msdn.com/b/ericlippert/archive/2008/02/20/…
【解决方案3】:

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

Here 就该错误来自规范的哪些部分进行了有趣的讨论。

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

在 C++ 中,以下是有效的(外部声明是在内部作用域之前还是之后并不重要,如果在之前,它会更有趣且更容易出错)。

void foo(int a)
{
    int count = 0;
    for(int i = 0; i < a; ++i)
    {
        int count *= i;
    }
    return count;
}

现在想象一下这个函数长了几行,可能很容易发现错误。编译器从不抱怨(不是过去,不确定 C++ 的新版本),并且函数总是返回 0。

这种行为显然是一个错误,因此如果 c++-lint 程序或编译器指出这一点会很好。如果它不是错误,只需重命名内部变量即可轻松解决它。

为了雪上加霜,我记得 GCC 和 VS6 对 for 循环中的计数器变量的归属有不同的看法。一个说它属于外部范围,另一个说它不属于。处理跨平台代码有点烦人。让我再举一个例子来增加我的行数。

for(int i = 0; i < 1000; ++i)
{
    if(array[i] > 100)
        break;
}

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

此代码在 VS6 IIRC 中有效,但在 GCC 中无效。反正C#清理了一些东西,挺好的。

【讨论】:

    猜你喜欢
    • 2013-01-24
    • 2022-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-18
    • 2017-02-12
    • 1970-01-01
    • 2017-08-19
    相关资源
    最近更新 更多