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.
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.
See more on this question at Stackoverflow