How come this gives a 's' does not exist in the current context
error (as expected):
public static void Main()
{
foreach(var i in new[]{1, 2, 3}) {
int s = i;
}
Console.WriteLine(s);
}
(ideone)
But this gives a 's' cannot be redeclared
error?
public static void Main()
{
foreach(var i in new[]{1, 2, 3}) {
int s = i;
}
int s = 4;
}
(ideone)
The first error tells me that s
doesn't exist outside of the foreach
, which makes sense, but the second error says otherwise. Why (and how!?) would I ever need to access a variable from a child scope?
The first error tells me that s doesn't exist outside of the foreach, which makes sense
Indeed - it's right.
but the second error says otherwise.
No, it doesn't. It's telling you that you can't declare the first (nested) variable s
, because the second one is already in scope. You can't access it before the declaration, but it's in scope for the whole block.
From the C# 5 specification, section 3.7:
• The scope of a local variable declared in a local-variable-declaration (§8.5.1) is the block in which the declaration occurs.
So yes, it extends upwards to the enclosing {
., basically.
And then from section 8.5.1:
The scope of a local variable declared in a local-variable-declaration is the block in which the declaration occurs. It is an error to refer to a local variable in a textual position that precedes the local-variable-declarator of the local variable. Within the scope of a local variable, it is a compile-time error to declare another local variable or constant with the same name.
That final part (emphasis mine) is why you're getting an error.
Why (and how!?) would I ever need to access a variable from a child scope?
Not sure what you mean here, but the rule is basically there to make it harder for you to write hard-to-read or fragile code. It means there are fewer places where moving the declaration of the variable up or down (but still in the same block, at the same level of nesting) produces code which is valid but with a different meaning.
Ok, first consider the following, simple class implementation.
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;
}
}
}
As we can see, many variables are declared at different locations of our method and it could be assumed that when the C# interpreter reads our file, it will organize the IL in such a way that declared objects will be defined where we wrote our variable definition code.
However, after compiling and inspecting our IL we see a much different story.
Class 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
Notice that all of our objects for our method have been gathered together and initialized in the .locals init...
call at the top of our method. If I find the time, I may do a little further digging on why the .Net IL is organized this way, but if I had to make an educated guess it would be that variable declaration has a certain level of overhead involved and that bundling together all variables within a particular scope might save a few CPU cycles, instead of making many calls to .locals init...
, every time that a new object is declared.
I hope that provides a little further clarity. Jons answer is correct by spec definition, but this could shed a little light on why the spec was written that way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With