views:

327

answers:

2

The following code fails to compile stating "A local variable named 'st' cannot be declared in this scope because it would give a different meaning to 'st', which is already used in a 'child' scope to denote something else":

        var l = new List<string>();
        l.Find(st => st.EndsWith("12"));
        string st = "why this fails?";

I understand why this won't work:

        string preParent = "";
        {
            string preParent = "Should fail cause we change the meaning";
        }

When we do the following we get "CS0103: The name 'postParent' does not exist in the current context":

        {
            string postParent=string.Empty;
        }
        postParent = "Should this work?";

What I don't get is why is the compiler smart enough to see that postParent is not within scope, but won't let me define a new variable that has the same name as a variable used within a child scope (which is obviously out of scope at this point).

Is the compiler simple enforcing scope by refusing to let me use the variable? If so this makes sense.

===========

Edited:

I guess what I also find interesting is how you can have the same variable within two child scopes in a single method, so this is valid:

        {
            string thisWorks= string.Empty;
        }
        {
            string thisWorks= "Should this work?";
        }

I'm just a little curious that you can have two variables with the same name as long as they are at the same level (if you look at scope as a tree). This makes sense because you can have local variables in two methods of the same class with the same name.

I'm just surprised that the compiler is able to differentiate and allow this, while it wouldn't allow the postParent variable. And is this a technical limitation or was this a design decision? That's what I'm really trying to get at;-)

+1  A: 

Yes, the compiler is enforcing scope. Note that the scope of a variable is the lexical block it's part of - not just from the point of declaration onwards, but the whole scope.

The compiler is complaining because the assignment to postParent is outside its scope (which is only the nested braces). If you tried to declare a new variable at the point where you're currently assigning to postParent the problem would be with the nested block, because the scope of postParent would include that nested block, even though it was before the declaration.

Scopes are described in section 3.7 of the C# 3.0 specification.

EDIT: To respond to your question edit.

It's just two simple rules:

  • you can't declare a local variable when another local variable with the same name is in scope
  • the scope of a local variable is the block in which the declaration occurs

I'm sure the language could have been designed such that the scope only began at the point of declaration, but I think it's simpler (in terms of language complexity) to consider scopes as just blocks - so all local variables declared in the same block have the same scope, for example. That makes life a lot simpler when considering captured variables, too - as what gets captured depends on the scope, and nested scopes make life interesting...

EDIT: The language spec has this to say about the original lambda expression example - it's section 7.14.1:

The optional anonymous-function-signature of an anonymous function defines the names and optionally the types of the formal parameters for the anonymous function. The scope of the parameters of the anonymous function is the anonymous-function-body. Together with the parameter list (if given), the anonymous-method-body constitutes a declaration space. For this reason, it is a compile-time error for the name of a parameter of the anonymous function to match the name of a local variable, local constant, or parameter whose scope includes the anonymous-method-expression or lambda-expression.

Does that help?

Jon Skeet
Regardless if that is why it enforces that rule, I still believe this is a defect in the CLR. Anonymous methods should definitely descope any variables created inside them from any other function, even it's as simple as the CLR adding a prefix to each variable in the delegate method.
Chris Marisic
The CLR isn't involved at all here. It's a language decision. And I disagree entirely. The point is that the outer variables are available to the anonymous function, so the anonymous function can't redefine them, in exactly the same way as if the variable were redeclared in a "normal" block.
Jon Skeet
Makes sense since assuming the declaration is part of the whole scope block you'd have no way to know if the outer variable should be rolled into the anonymous function or not. If it was based on where the variable was scoped from you could allow this.
JoshBerke
I aggree that it makes life simpler...although it would be nice if the symantics in how a variable is used matches the scope. I just found this kind of interesting and now that I'm using lambda's alot run into this (especially in my throw away prototype code where I somtimes get lazy and use i, t..
JoshBerke
And why shouldn't you be able to redefine the variable? You can hide variables with poor programming in classes. Declare a member variable and then inside a method declare a variable with the same name and the compiler doesn't care. It should be the same here.
Chris Marisic
I disagree with the language specification enforcing rules on intended practice such as requiring case statements to have breaks. Case fall through should be allowed and given a warning not a compile error. This is the same situation, and you're quite nitpicky over when i said CLR earlier.
Chris Marisic
Chris: I guess we'll have to agree to disagree. I like the way the language works for switch/case, too. (There's always goto if you really want to...) (And I certainly disagree that it's nitpicky to distinguish between the runtime and the language. It's a very important difference.)
Jon Skeet
I don't think that scope is the issue here. The question is why the C# compiler doesn't let you declare a variable of the same name as a prior child scope. In this example "st" is declared in the child lambda scope - it is no longer in scope by the time that it is declared via 'string st = "why this fails?";' There is no language reason for this - it must be to help the programmer avoid hard-to-debug code.
JoshL
Scope *is* the reason that the compiler doesn't let you do it, but you've got the problem the wrong way round. The reason the compiler is issuing an error is that the *latter* string is already in scope when the *lambda* expression tries to declare st as a parameter name. I dare say the language could work round it, but it's made a choice.
Jon Skeet
A: 

You're declaring a variable in a limited scope and trying to use it outside of that scope. The compiler assumes you don’t want access to it so you can declare a variable with the same name somewhere else in the file. Your trying to do the old C trick of assuming the variable will live immediately outside of the scope. For example this used to work in older versions of C/C++ but no longer does.

for (int i=0; i<10; i++)
{
    cout <<”In the loop i is “<< i << endl;
}
cout << “outside of the loop i is “ << i << endl; //this only compiles with old C/C++ compilers.
Jared
Jared that is not what I was trying to do;-) C# doesn't let you declare a variable with same name if that variable is declared within a child scope.
JoshBerke