views:

515

answers:

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

string var = "New VAR!";

This will result in:

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.

Nothing earth shattering really, but isn't this just plain wrong? A fellow developer and I were wondering if the first declaration should be in a different scope, thus the second declaration cannot interfere with the first declaration.

Why is C# unable to differentiate between the two scopes? Should the first IF scope not be completely separate 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.

+18  A: 

The issue here is largely one of good practice and preventing against inadvertent mistakes. Admittedly, the C# compiler could theoretically be designed such that there is no conflict between scopes here. This would however be much effort for little gain, as I see it.

Consider that if the declaration of var in the parent scope were before the if statement, there would be an unresolvable naming conflict. The compiler simply does not differentiate between the following two cases. Analysis is done purely based on scope, and not order of declaration/use, as you seem to be expecting.

The theoretically acceptable (but still invalid as far as C# is concerned):

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

string var = "New VAR!";

and the unacceptable (since it would be hiding the parent variable):

string var = "New VAR!";

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

are both treated precisely the same in terms of variables and scopes.

Now, is there any actual reason in this secenario why you can't just give one of the variables a different name? I assume (hope) your actual variables aren't called var, so I don't really see this being a problem. If you're still intent on reusing the same variable name, just put them in sibling scopes:

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

{
    string var = "New VAR!";
}

This however, while valid to the compiler, can lead to some amount of confusion when reading the code, so I recommend against it in almost any case.

Noldorin
Of course the compiler would be able to differentiate the two scopes, the inner variable would only hide the outer, and make it unreachable.
Mats Fredriksson
Yeah exactly - it's disallowed simply because it can lead to confusion/is considered bad design practice.
Noldorin
well seen from the IL view point Im pretty sure the if{} scope doesn't exist so it would mean having the compiler do tricks for the value of potential confusion, so if im correct it's not actively disallowed but passively. The needed feature is simply not implemented
Rune FS
By the way, you said in a comment on a deleted post that a conditional statement creates a scope. It certainly does not. A *block* creates a scope. Numerous statements do create scopes -- for, foreach, using, try/catch/finally, and so on. However, the conditional statement is not one of them; the scope created is created by the block, not by the conditional.
Eric Lippert
@Eric: Trust you to point something like that out. :) You are, of course, right, though it's a rather subtle point. It is effectively the curly brackets that signify a block, with the notable exceptions you pointed out, that signify a block. Correct me if I'm wrong, but if statements with one-line actions do not contain child scope(s)?
Noldorin
Correct.........
Eric Lippert
+8  A: 

This is valid in C++, but a source for many bugs and sleepless nights. I think the C# guys decided that it's better to throw a warning/error since it's, in the vast majority of cases, a bug rather than something the coder actually want.

Here's an interesting discussion on what parts of the specification this error comes from.

EDIT (some examples) -----

In C++, the following is valid (and it doesn't really matter if the outer declaration is before or after the inner scope, it will just be more interesting and bug-prone if it's before).

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

Now imagine the function being a few lines longer and it might be easy to not spot the error. The compiler never complains (not it the old days, not sure about newer versions of C++), and the function always returns 0.

The behaivour is clearly a bug, so it would be good if a c++-lint program or the compiler points this out. If it's not a bug it is easy to work around it by just renaming the inner variable.

To add insult to injury I remember that GCC and VS6 had different opinions on where the counter variable in for loops belonged. One said it belonged to the outer scope and the other said it didn't. A bit annoying to work on cross-platform code. Let me give you yet another example to keep my line count up.

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);

This code worked in VS6 IIRC and not in GCC. Anyway, C# has cleaned up a few things, which is good.

Mats Fredriksson
good point, I hadn't considered this as a possible explanation
Stefan Monov
+10  A: 

isn't this just plain wrong?

No, this is not wrong at all. This is a correct implementation of section 7.5.2.1 of the C# specification, "Simple names, invariant meanings in blocks".

The specification states:


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?

The question is nonsensical; obviously the compiler is able to differentiate between the two scopes. If the compiler were unable to differentiate between the two scopes then how could the error be produced? The error message says that there are two different scopes, and therefore the scopes have been differentiated!

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

No, it should not. The scope (and local variable declaration space) defined by the block statement in the consequence of the conditional statement is lexically a part of the outer block which defines the body of the method. Therefore, rules about the contents of the outer block apply to the contents of the inner block.

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.

This is completely wrong. It is specious to conclude that just because the local variable is no longer in scope, that the outer block does not contain an error. The error message is correct.

The error here has nothing to do with whether the scope of any variable overlaps the scope of any other variable; the only thing that is relevant here is that you have a block -- the outer block -- in which the same simple name is used to refer to two completely different things. C# requires that a simple name have one meaning throughout the block which first uses it.

For example:

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

That is perfectly legal; the scope of the outer x overlaps the scope of the inner x, but that is not an error. What is an error is:

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

because now the simple name "x" means two different things inside the body of M -- it means "this.x" and the local variable "x". It is confusing to developers and code maintainers when the same simple name means two completely different things in the same block, so that is illegal.

We do allow parallel blocks to contain the same simple name used in two different ways; this is legal:

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

because now the only block that contains two inconsistent usages of x is the outer block, and that block does not directly contain any usage of "x", only indirectly.

Eric Lippert
@Eric: I feel like, among other things, http://blogs.msdn.com/ericlippert/archive/2009/08/03/what-s-the-difference-part-two-scope-vs-declaration-space-vs-lifetime.aspx is relevant here.
Brian
Good point; though in fact the issue of whether we're talking about the block as a local variable declaration space, or the block as a scope of a local variable, is pretty much irrelevant to the question. The problem *appears* to be about declaration space, since the declaration is flagged. But in fact it is about *scope* -- we have two entities which are both looked up by their unqualified name successfully, and bound to different entities in the same block.
Eric Lippert
The example with field and local variable with the same name being accessed from the same block is probably the single most illuminating with respect to the rationale of this design decision.
Pavel Minaev