Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# variable scoping: 'x' cannot be declared in this scope because it would give a different meaning to 'x'

Tags:

scope

c#

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.


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.


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.