7

I just bumped into a really strange C# behavior, and I’d be glad if someone could explain it to me.

Say, I have the following class:

class Program
{
    static int len = 1;
    static void Main(string[] args)
    {
        Func<double, double> call = len => 1;
        len = 1; // error: 'len' conflicts with the declaration 'csutils.Program.len'
        Program.len = 1; // ok
    }
}

As I get it, in the line of commentary I have the following objects in my field of view: len variable and call. Inside of lambda, I have local parameter len and Program.len variable.

However, after declaring such lambda, I cannot use len variable in the scope of Main method anymore. I have to either refer to it as to Program.len either rewrite lambda to be anyOtherNameBesidesLen => 1.

Why is it happening? Is this correct behavior of language, or I’ve encountered a bug in the language? If this is correct behavior, how is it justified by the language architecture? Why is it okay for lambda capture variable to mess with code outside lambda?

Edit: Alessandro D'Andria has got quite nice examples (number 1 and 2 in his comment).

edit2: This code (equal to the one I wrote at the beginning) is illegal:

class Program
{
    static int len = 0;
    static void Main(string[] args)
    {
        {
            int len = 1;
        }
        int x = len;
    }
}

This code however, despite having exactly the same scope structure, is perfectly legal:

class Other
{
    static int len = 0;
    class Nested
    {
        static void foo()
        {
            int len = 1;
        }
        static int x = len;
    }
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
Dantragof
  • 155
  • 6
  • 11
    `or i've encountered a bug in the language` I liked your self confidence. – L.B Mar 15 '14 at 18:28
  • Just to clarify, if on the next line you have something like `len = 9000`, it throws a compiler error. EDIT: At this point, removing the lambda or renaming the lambda's input parameter "len" to something else, it will compile. – Chris Sinclair Mar 15 '14 at 18:31
  • @L.B. Well, you always have to consider everything, don't you? – Dantragof Mar 15 '14 at 18:32
  • 1
    @Dantragof, the title of your quetion is essentially the answer to your question. Yes, doing what you're doing creates an ambiguitiy. – Kirk Woll Mar 15 '14 at 18:32
  • @Kirk Woll, well, i know it creates ambiguity, because, well, it creates it. I am more interested in Why it creates it. Why does lambda capture variable have anything to do with external scope? What is the reasoning behind this implementation of capture? – Dantragof Mar 15 '14 at 18:33
  • 4
    @Dantragof, it's analogous to if you declare a variable of the same name in a block. i.e. `{ var len = 5; }` would create an ambiguity as well. – Kirk Woll Mar 15 '14 at 18:35
  • @KirkWoll, thanks, that clears it up a bit. It seems the rules of overriding a variable in a nested scope are ambiguous by themselves. Under ambiguity in this case i refer to how sometimes you can re-declare a variable from parent scope, and sometiems you can't. – Dantragof Mar 15 '14 at 18:42
  • @KirkWoll I'm not sure I fully understand this either. If you remove the static 'len', then the compiler will dislike the len=1 line because the name 'len' doesn't exist in the current context, implying the lambda's parameters aren't available outside the lambda's scope (which makes sense). So if that len can't be referring to the lambda parameter, where's the ambiguity? – Ben Aaronson Mar 15 '14 at 18:48
  • 2
    Summoning @EricLippert – AgentFire Mar 15 '14 at 18:59
  • 4
    @AgentFire [He has a blog post about this already...](http://blogs.msdn.com/b/ericlippert/archive/2009/11/02/simple-names-are-not-so-simple.aspx) – Servy Mar 15 '14 at 19:05

2 Answers2

4

As far as I can see, it is correct to emit a compile-time error in this case because it is not permissible to use len in a child scope (namely the parameter of the anonymous function (lambda)) when the same symbol len without qualification is used (for something else) in a containing scope within the same method.

However, the error text is confusing.

If you change to:

static int len = 1;
static void Main(string[] args)
{
    len = 1;
    Func<double, double> call = len => 1;  // error CS0136: A local variable named 'len' cannot be declared in this scope because it would give a different meaning to 'len', which is already used in a 'parent or current' scope to denote something else
}

the error text is better.


Some other examples:

static int len = 1;
static void Main()
{
    var len = 3.14; // OK, can hide field

    Console.WriteLine(len); // OK, 'len' refers to local variable

    Console.WriteLine(Program.len); // OK, hidden field can still be accessed, with proper qualification
}

The above example shows that it is fine to hide a field with an identically named local variable (or method parameter) as long as the field is always accessed with qualification (after a . member access operator).

static int len = 1;
static void Main()
{
    if (DateTime.Today.DayOfWeek == DayOfWeek.Saturday)
    {
        var len = 3.14;

        Console.WriteLine(len);
    }

    Console.WriteLine(len); // error CS0135: 'len' conflicts with the declaration 'csutils.Program.len'
}

This shows that it is not possible to hide len in a child scope when you try to use the field len in the parent scope. Again the error text can be criticized.

static int len = 1;
static void Main()
{
    Console.WriteLine(len);

    if (DateTime.Today.DayOfWeek == DayOfWeek.Saturday)
    {
        var len = 3.14;  // error CS0136: A local variable named 'len' cannot be declared in this scope because it would give a different meaning to 'len', which is already used in a 'parent or current' scope to denote something else

        Console.WriteLine(len);
    }
}

You see the analogy.


Of course these issues have been mentioned many times before here on SO, an example being Why can't a duplicate variable name be declared in a nested local scope?

Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • yeah, such error message would be better indeed. However it's still confusing that it's completely different situations when you 'can use a variable, but do not use it' and 'can use a variable and use it'. – Dantragof Mar 15 '14 at 19:55
0

The error message is erroneous and it should point to the line containing the lambda.

As you say "Inside of lambda, I have local parameter len and Program.len variable", the problem is that the name of the lambda parameter conflicts with the variable len. So the problem is in the lambda expression only. The problem is not that you "cannot use len variable in the scope of Main method anymore". It is a conflict between the two and no code at all will compile anymore.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188