22

I've got a weak imagination when it comes to names, so I often find myself re-using identifiers in my code. This caused me to run into this specific problem.

Here's some example code:

public delegate void TestDelegate(int test);

public class Test
{
    private int test;

    private void method(int aaa)
    {
        TestDelegate del = test => aaa++;

        test++;
    }

    public static void Main()
    {
    }
}

Here are the compilation errors (output by ideone):

prog.cs(11,3): error CS0135: `test' conflicts with a declaration in a child block
prog.cs(9,22): (Location of the symbol related to previous error)
Compilation failed: 1 error(s), 0 warnings

Line 11 contains test++, line 9 contains the lambda.

Incidentally, Visual Studio 2013 gives a different error:

'test' conflicts with the declaration 'Namespace.Test.test'

The error occurs at the increment on line 11 only.

The code compiles successfully if I comment out either line 9 (the lambda) or line 11 (the increment).

This issue is a surprise to me - I was sure that lambda parameter names can conflict only with local method variable names (which is sort of confirmed by the code compiling when I comment out the increment). Also, how can the lambda parameter possibly affect the increment, which is right outside the lambda's scope?

I can't get my head around this... What exactly did I do wrong? And what do the cryptic error messages mean in this case?

EDIT after all the great answers:

So I think I finally understood the rule that I broke. It is not well-worded in the C# spec (7.6.2.1, see Jon Skeet's answer for the quote). What it was supposed to mean is something like:

You can not use the same identifier to refer to different things (entities) in the same "local variable declaration space" if one of the offending uses is (directly) located in a scope which can be "seen" from the scope where the other is (directly) located.

Not the standard's standard phrasing, but I hope you understood what I mean. This rule was supposed to allow this:

{
    int a;
}

{
    int a;
}

because neither of the scopes of the two variables a can be "seen" from the other's scope;

and disallow this:

{
    int a;
}

int a;

because the second variable declaration is "seen" from the first variable's scope

and disallow this:

class Test
{
    int test;

    void method()
    {
        {
            int test;
        }

        test++;
    }
}

because the increment of the field can be "seen" from the block's scope (it not being a declaration doesn't matter).

It seems that C#6 changed this rule, specifically making the last example (and my original code) legit, though I don't really understand how exactly.

Please correct me if I made some mistakes in these examples.

Amal Dev
  • 1,938
  • 1
  • 14
  • 26
  • 1
    That code compiles for me with Roslyn and with Mono 4.1.0.0 (built from head a few weeks ago). – Jon Skeet Apr 20 '15 at 14:29
  • @JonSkeet: whoa, so this could be a *bug* in csc? O_o (gosh, I feel so cool right now) –  Apr 20 '15 at 14:30
  • 2
    Aha, now reproduced with csc from .NET 3.5. Yes, I suspect it's a bug in the compiler. – Jon Skeet Apr 20 '15 at 14:31
  • 7
    besids the question of bug or not. Dont programm like that. it's very confusing to the reader. – AK_ Apr 20 '15 at 14:34
  • You should give test a value before you do ++ to it, also I agree with @AK_ don't program like this. – Thealon Apr 20 '15 at 14:45
  • @Thealon: It's fine to use `++` on a *field* which is never assigned a value explicitly. It will have the default value to start with. – Jon Skeet Apr 20 '15 at 17:06

3 Answers3

13

Eric Lippert has blogged about this Simple names are not so simple.

Simple names(without a fully qualified name) can always mean only one thing in a block of code. If you violate it there will be a CS0135 compiler error.

In your method method, test is a simple name, which means two things. It is not allowed in c#.

If you make the test field you use a qualified name instead of simple name, compiler error will go away.

private void method(int aaa)
{
    TestDelegate del = test => aaa++;

    this.test++;
}

Or, if you make the test field access in a different block, compiler will be happy to compile.

private void method(int aaa)
{
    TestDelegate del = (int test) => aaa++;

    {
        test++;
    }
}

Now you don't have two different meaning for the same simple name test. because second test lives in a different block.

As of now(april 2015) this answer is valid. Starting from C#6.0 things has changed. This rule has gone away. Refer Jon's answer for details.

Community
  • 1
  • 1
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • try adding a method called `void foo(int test){}` then try commenting out the line with the `++` . OP is 100% right this is a bug in the compiler. also to the best of my understanding this has nothing to do with Eric's blog post. – AK_ Apr 20 '15 at 14:55
  • @AK_ What's up with your `foo` method? I don't understand. Why would you claim this a bug in the compiler? Eric's post is about the same topic. Simple names can't mean different things in a same declaration scope. – Sriram Sakthivel Apr 20 '15 at 14:59
  • @JonSkeet I'm not sure about when the behavior changed. I have a C# 5.0 compiler and it doesn't work there. – Sriram Sakthivel Apr 20 '15 at 15:00
  • 1
    @SriramSakthivel: Do you mean declaration space? Note that the declaration space of the parameter is the lambda expression, and the declaration space of the field is the class. – Jon Skeet Apr 20 '15 at 15:01
  • try adding my method foo to the class in the question and compile it. you'll understand why is this a bug. – AK_ Apr 20 '15 at 15:01
  • (And it's fine to declare a local variable with the same name as a field, for example.) – Jon Skeet Apr 20 '15 at 15:02
  • @JonSkeet I'm aware that you can have a local variable with the same name of the field. But you can mix it up accessing the both in a single method with the simple name. You need to qualify the field with this qualifier. – Sriram Sakthivel Apr 20 '15 at 15:06
  • @JonSkeet Eric has answered a related [question here](http://stackoverflow.com/questions/2630244/c-sharp-going-nuts-when-i-declare-variables-with-the-same-name-as-the-ones-in-a/2630696#2630696). He also refers to the same blogpost. – Sriram Sakthivel Apr 20 '15 at 15:11
  • @SriramSakthivel `test` in the lambda is a parameter... like a method parameter... – AK_ Apr 20 '15 at 15:12
  • @AK_ Refer this [answer from eric lippert](http://stackoverflow.com/questions/2630244/c-sharp-going-nuts-when-i-declare-variables-with-the-same-name-as-the-ones-in-a/2630696#2630696). I just found out. It is a very related one. he also refers to the same blog post which I linked. That is a different method is an implementation detail. Not with the c# terms. In c# terms it is just a feature. You don't need to know it is a different method parameter or not. – Sriram Sakthivel Apr 20 '15 at 15:13
  • Even if it is a feature, doesn't seem quite uniform to me. Here `var num = Enumerable.Range(0, 5).Select(test => test).Where(test => test / 2 == 0); var test = 5;` The `test` in `Where` clause is perfectly compiled. But `test` in the 2nd statement cannot be compiled. I never understood this. – wonderbell Apr 20 '15 at 15:27
  • @aSharma That doesn't compile for me. I'm using c# 5.0 compiler. If you remove the `var test = 5;` it compiles. – Sriram Sakthivel Apr 20 '15 at 15:36
  • 1
    @aSharma: that's a different error. You can't have a lambda parameter with the same name as a variable in the containing block, whether it is declared before or after the lambda. –  Apr 20 '15 at 15:59
  • 1
    @SriramSakthivel: That doesn't look very closely related to me. In the question you're referring to, there's another *local* variable declaration in scope. That's not the case here - there's just the lambda expression parameter, and the field. That's fine, as far as I'm aware. I still believe this is a bug in the C# 5 compiler. – Jon Skeet Apr 20 '15 at 16:16
  • Hmm. Looking further, I *suspect* this is going to be a change in C# 6, to section 7.6.2.1 of the specification. – Jon Skeet Apr 20 '15 at 16:25
  • 1
    @JonSkeet: I believe you are correct. I did a bunch of work on that error message for C# 6 before I left, and more work was done on it after I left. I suspect that the rule has been changed in some subtle way in C# 6. Neal would probably be a good person to ask; he and I discussed some ways to change that rule several times. – Eric Lippert Apr 20 '15 at 17:17
  • @EricLippert: I've raised a bug (see my answer for a link), so we'll see :) At the very least I'd like to see the wording change,. – Jon Skeet Apr 20 '15 at 17:19
  • @SriramSakthivel: After looking through this a bit, I think you're along the right lines, but the wording of both your answer *and the spec* is a bit ambiguous. – Jon Skeet Apr 20 '15 at 17:19
  • @JonSkeet I was confident that my answer is right. That's the reason why I didn't deleted it even after couple of downvotes. The master eric lippert confirmed it. I wasn't sure my terminology is right. If I used "scope" or "declaration space" that would have given different meaning, that's why I used the term "block" but unfortunately block is a vague and ambiguous thing. – Sriram Sakthivel Apr 20 '15 at 18:23
  • 1
    @SriramSakthivel: Exactly - because as per the second example in my answer, you *can* have two meanings within the same block of code, if you include nesting. It's really, "one name can't refer to two different variables within the same block (including nesting) where the immediate block (*not* including nesting) includes at least one use of that name" - which is an ugly mouthful. – Jon Skeet Apr 20 '15 at 18:26
  • (And I still think the question you linked to is unfortunate, as it demonstrates error CS0136, not CS0135. That's what introduced the most confusion, IMO. I would keep the links to Eric's blog, but get rid of the link to that SO post.) – Jon Skeet Apr 20 '15 at 18:30
  • @JonSkeet that's mouthful, but it exactly explains the rule without any confusing terms. I've removed the link to a another question (though I mentioned it is just a related question, just to say there is such a crazy rule) – Sriram Sakthivel Apr 20 '15 at 18:40
  • @JonSkeet Do we have unambiguous terminology now for all of these? I guess not? If yes please link me there. – Sriram Sakthivel Apr 20 '15 at 18:40
  • @SriramSakthivel: I don't have any more concise terminology, no. As per my answer, this is changing for C# 6 anyway (the rule is going away). – Jon Skeet Apr 20 '15 at 18:43
9

The unfortunately downvoted answer of Sriram Sakthivel is correct. C# has a rule, which I have written about a number of times, which requires that every usage of the same simple name throughout a block to have the same meaning.

I agree that the error message is extremely confusing. I did extensive work in Roslyn to ensure that this error message was less confusing in Roslyn, work which may have been for naught.

You can read my articles about this rule, and the work I did to improve the error message, here:

http://ericlippert.com/tag/simple-names/

(Start at the bottom; these are in reverse-chronological order.)

Jon and others correctly note that in prerelease versions of C# 6, you don't get the error for your code. I believe that after I left the team, more work was done on this error condition, and likely it has been relaxed to be more lenient.

The principle of "one name must mean only one thing" is a good one, but it is tricky to implement, tricky to explain, and the source of a lot of questions on this site, so probably the design team decided to go with an easier-to-explain-and-implement rule. What exactly that rule is, I don't know; I have not yet had time to browse the Roslyn source code and see how that part of the code has evolved over time.

UPDATE: My spies in the Roslyn team inform me that it was commit 23891e, which will shorten the search considerably. :-)

UPDATE: The rule is gone for good; see Jon's answer for details.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 2
    Is "throughout a block" meant to include nested blocks though? If so, it's trivial to violate by declaring two local variables in different nested blocks. If not, it's hard to see how it's violated by code which only uses the variable once per non-nested block. – Jon Skeet Apr 20 '15 at 17:23
  • 2
    @JonSkeet: I agree that the wording is unclear. The intention is that the name must be unique throughout the "highest" local declaration space that *directly* contains a usage of a simple name. This is deliberately intended to allow `{ int x; } { int x; }` and `for(int x...) {} for (int x...) { }` and `Where(x=>y(x)).Select(x=>z(x))` – Eric Lippert Apr 20 '15 at 17:37
  • Right. Glad it's as I suspected, at least. Now to try to work out how to express that more clearly... – Jon Skeet Apr 20 '15 at 17:53
  • 1
    @JonSkeet: I just remembered, Andy messaged me when I blogged about this earlier and said that the change was in commit 23891e . I haven't investigated further. – Eric Lippert Apr 20 '15 at 18:23
4

I raised Roslyn issue 2110 for this - the C# 6 spec is changing to allow this. Mads Torgersen has indicated that the change is by design:

The rule was well intended, in that it was supposed to minimize the risk of "moving code around" in a way that would silently change its meaning. However, everyone we talked to only seemed to know about the rule from when it was causing them unnecessary grief - no-one had ever been saved by its prohibitions.

Furthermore, the extra tracking necessary in the compiler to enforce the rule interactively was causing significant complications to the code, as well as non-trivial performance overhead. Now, if it were a good rule we would have kept it anyway, but it isn't! So it's gone.

It's simple to demonstrate the inconsistency between compiler versions without any delegates involved:

class Test
{
    static int test;

    static void Main()
    {
        {
            int test = 10;
        }
        test++;
    }
}

The related section of the C# 5 spec is 7.6.2.1, which gives this rule:

7.6.2.1 Invariant meaning in blocks

For each occurrence of a given identifier as a full simple-name (without a type argument list) in an expression or declarator, within the local variable declaration space (§3.3) immediately enclosing that occurrence, every other occurrence of the same identifier as a full 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.

Personally, I think this is a less-than-ideal bit of spec. It isn't clear whether "within a given block" is meant to include nested blocks. For example, this is fine:

static void Main()
{
    {
        int x = 10;
    }
    
    {
        int x = 20;
    }
}

... despite the fact that x is used to refer to different variables within the "top-level" block of the Main method, if you include nesting. So if you consider that block, it violates the claim that "this rule ensures that the meaning of a name is always the same within a given block [...]" However, I believe that block isn't checked for this, because it isn't the "immediately enclosing" variable declaration space of any use of x.

So in my view, the error is consistent with the first quoted part of the spec, but isn't consistent with the final sentence, which is a note in the ECMA spec.

I will make a note of the poor wording of the spec, and try to fix it for the next version of the ECMA spec.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • That's amazing... The wording *can* be interpreted so that code like my example or your first example is illegal... but it just sounds so wrong, nested blocks have their own (more-or-less) separate scope! –  Apr 20 '15 at 17:23
  • @Mints97: My first example is very similar to an example in the spec which is given as an example of something *meant* to be illegal. Weird, I know. – Jon Skeet Apr 20 '15 at 17:24