可变范围

时间:2015-03-06 15:02:00

标签: c#

为什么会出现's' does not exist in the current context错误(如预期的那样):

public static void Main()
{
    foreach(var i in new[]{1, 2, 3}) {
        int s = i;
    }

    Console.WriteLine(s);
}

ideone

但这会产生's' cannot be redeclared错误?

public static void Main()
{
    foreach(var i in new[]{1, 2, 3}) {
        int s = i;
    }

    int s = 4;
}

ideone

第一个错误告诉我s以外的foreach不存在,这是有道理的,但第二个错误则另有说明。为什么(以及如何!?)我需要从子范围访问变量?

4 个答案:

答案 0 :(得分:12)

  

第一个错误告诉我s在foreach之外不存在,这是有道理的

确实 - 这是对的。

  

但第二个错误则另有说明。

不,它没有。它告诉你,你不能声明第一个(嵌套)变量s,因为第二个已经在范围内了。您无法在声明之前访问它,但它在整个块的范围中。

从C#5规范,第3.7节:

  

•在local-variable-declaration(第8.5.1节)中声明的局部变量的范围是声明发生的块。

所以是的,它基本上向上延伸到封闭的{

然后从第8.5.1节开始:

  

在local-variable-declaration中声明的局部变量的范围是声明发生的块。在局部变量的local-variable-declarator之前的文本位置引用局部变量是错误的。 在局部变量的范围内,声明另一个具有相同名称的局部变量或常量是编译时错误。

最后一部分(强调我的)是你收到错误的原因。

  

为什么(以及如何!?)我需要从子范围访问变量?

不确定这里的意思,但规则基本上是为了让你更难以编写难以阅读或脆弱的代码。这意味着向上或向下移动变量声明的位置较少(但仍然在同一个块中,在相同的嵌套级别)会产生有效但具有不同含义的代码。

答案 1 :(得分:1)

好的,首先考虑以下简单的类实现。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestNamespace
{
    class ILOrderTest
    {
        public int DeclarationTests()
        {
            int intDeclaredAtTop = 0;

            for (int intDeclaredInForLoopDef = 0; intDeclaredInForLoopDef < 10; intDeclaredInForLoopDef++)
            {
                int intDeclaredInForLoopBody = intDeclaredInForLoopDef;
                intDeclaredAtTop = intDeclaredInForLoopBody;
            }
            int intDeclaredAfterForLoop;
            intDeclaredAfterForLoop = intDeclaredAtTop;
            return intDeclaredAfterForLoop;
        }
    }
}

正如我们所看到的,在我们方法的不同位置声明了许多变量,并且可以假设当C#解释器读取我们的文件时,它将以这样的方式组织IL,即在我们写入的地方定义声明的对象我们的变量定义代码。

然而,在编制和检查我们的IL后,我们看到了一个完全不同的故事。

类ILOrderTest IL

.method public hidebysig 
    instance int32 DeclarationTests () cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 38 (0x26)
    .maxstack 2
    .locals init (
        [0] int32 intDeclaredAtTop,
        [1] int32 intDeclaredInForLoopDef,
        [2] int32 intDeclaredInForLoopBody,
        [3] int32 intDeclaredAfterForLoop,
        [4] int32 CS$1$0000,
        [5] bool CS$4$0001
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: ldc.i4.0
    IL_0004: stloc.1
    IL_0005: br.s IL_0011
    // loop start (head: IL_0011)
        IL_0007: nop
        IL_0008: ldloc.1
        IL_0009: stloc.2
        IL_000a: ldloc.2
        IL_000b: stloc.0
        IL_000c: nop
        IL_000d: ldloc.1
        IL_000e: ldc.i4.1
        IL_000f: add
        IL_0010: stloc.1

        IL_0011: ldloc.1
        IL_0012: ldc.i4.s 10
        IL_0014: clt
        IL_0016: stloc.s CS$4$0001
        IL_0018: ldloc.s CS$4$0001
        IL_001a: brtrue.s IL_0007
    // end loop

    IL_001c: ldloc.0
    IL_001d: stloc.3
    IL_001e: ldloc.3
    IL_001f: stloc.s CS$1$0000
    IL_0021: br.s IL_0023

    IL_0023: ldloc.s CS$1$0000
    IL_0025: ret
} // end of method ILOrderTest::DeclarationTests

请注意,我们方法的所有对象都已收集在一起,并在方法顶部的.locals init...调用中初始化。如果我找到时间,我可能会进一步挖掘为什么 .Net IL以这种方式组织,但如果我必须做出有根据的猜测,那就是变量声明具有一定的水平所涉及的开销以及将特定范围内的所有变量捆绑在一起可能会节省一些CPU周期,而不是每次声明新对象时都会多次调用.locals init...

我希望这进一步澄清。根据规范定义,Jons的回答是正确的,但是这可以解释为什么规范是这样编写的。

答案 2 :(得分:0)

如果您想要两个名称为s的不同变量,您可以执行以下操作:

static void Main(string[] args) {
    foreach(var i in new[]{1, 2, 3}) {
        int s = i;
    }

    // better describe what you do in this scope
    // (and let others know that this is not just an ordinary if-clause)
    {
        int s = 4;
    }
}

但在大多数情况下,您需要在范围之外声明s并使用默认值初始化:

static void Main(string[] args) {
    int s = default(int);
    foreach(var i in new[] { 1, 2, 3 }) {
        s = i;
        //use s in loop
    }
    //use s after loop
}

答案 3 :(得分:0)

在这方面,C#就像Java一样:它不允许你在不同的范围级别中声明一个具有相同名称的变量,即使变量的第一个实例超出了范围。

可以在C和C ++中执行此操作。

我认为这取决于一种思想流派,即如果允许这样的行为,代码就会变得难以理解/不稳定。