Lambda parameter conflicting with class field on accessing field in later scope

本小妞迷上赌 提交于 2019-12-04 23:59:05

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.

Sriram Sakthivel

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!