views:

739

answers:

6

I found the following rather strange. Then again, I have mostly used closures in dynamic languages which shouldn't be suspectable to the same "bug". The following makes the compiler unhappy:

VoidFunction t = delegate { int i = 0; };

int i = 1;

It says:

A local variable named 'i' cannot be declared in this scope because it would give a different meaning to 'i', which is already used in a 'child' scope to denote something else

So this basically means that variables declared inside a delegate will have the scope of the function declared in. Not exactly what I would have expected. I havn't even tried to call the function. At least Common Lisp has a feature where you say that a variable should have a dynamic name, if you really want it to be local. This is particularly important when creating macros that do not leak, but something like that would be helpful here as well.

So I'm wondering what other people do to work around this issue?

To clarify I'm looking for a solution where the variables I declare in the delegete doesn't interfere with variables declared after the delegate. And I want to still be able to capture variables declared before the delegate.

+6  A: 

It has to be that way to allow anonymous methods (and lambdas) to use local variables and parameters scoped in the containing method.

The workarounds are to either use different names for the variable, or create an ordinary method.

Arjan Einbu
But the variable i is declared *after* the delegate. It really doesn't help much that the compiler tells me that I can't declare variables with the same name in that scope. I might not even call the function in that scope.
Anders Rune Jensen
A delegate does not have an independant scope any more than an if or foreach code block would have. See my answer for details.
AnthonyWJones
Whether you declare the variable i before or after the delegate is irrelevant. When compiled into IL, the variable i becomes a local variable, allocated on the stack at function call time. It does not "start existing" in the middle of the function. (Of course, if a delegate or lambda expression uses i, then it is compiled into a class field rather than a local variable, but that's a separate topic.)
Timwi
A: 

It's because the delegate can reference variables outside the delegate:

int i = 1;
VoidFunction t = delegate { Console.WriteLine(i); };
dalle
A: 

If I remember correctly, the compiler creates a class member of the outside variables referenced in the anonymous method, in order to make this work.

Here is a workaround:

class Program
    {
        void Main()
        {
            VoidFunction t = RealFunction;
            int i = 1;
        }
        delegate void VoidFunction();
        void RealFunction() { int i = 0; }
    }
Øyvind Skaar
+3  A: 

The "closure" created by an anonymous function is somewhat different from that created in other dynamic languages (I'll use Javascript as an example).

function thing() {
    var o1 = {n:1}
    var o2 = {dummy:"Hello"}
    return function() { return o1.n++; }
}

var fn = thing();
alert(fn());
alert(fn());

This little chunk of javascript will display 1 then 2. The anonymous function can access the o1 variable because it exists on its scope chain. However the anonymous function has an entirely independant scope in which it could create another o1 variable and thereby hide any other further down the scope chain. Note also that all variables in the entire chain remain, hence o2 would continue to exist holding an object reference for as long as the fn varialbe holds the function reference.

Now compare with C# anonymous functions:-

class C1 { public int n {get; set;} }
class C2 { public string dummy { get; set; } }

Func<int> thing() {
   var o1 = new C1() {n=1};
   var o2 = new C2() {dummy="Hello"};
   return delegate { return o1.n++; };
}
...
Func<int> fn = thing();
Console.WriteLine(fn());
Console.WriteLine(fn());

In this case the anonymous function is not creating a truely independant scope any more than variable declaration in any other in-function { } block of code would be (used in a foreach, if, etc.)

Hence the same rules apply, code outside the block cannot access variables declared inside the block but you cannot reuse an identifier either.

A closure is created when the anonymous function is passed outside of the function that it was created in. The variation from the Javascript example is that only those variables actually used by the anonymous function will remain, hence in this case the object held by o2 will be available for GC as soon as thing completes,

AnthonyWJones
Thanks, but why would they ever created such horrible scope rules? In which case does it make sense that scope local variables blocks creating outer variables with the same name? It should at most be a warning?
Anders Rune Jensen
Its a good question and I'm not sure I know the answer. Its likely related to performance and the ability of the compiler to optimise. Perhaps you can ask it specifically in SO.
AnthonyWJones
Good idea, asked here: http://stackoverflow.com/questions/405116/why-is-the-scope-of-if-and-delegates-this-way-in-c
Anders Rune Jensen
+1  A: 

You'll also get CS0136 from code like this:

  int i = 0;
  if (i == 0) {
    int i = 1;
  }

The scope of the 2nd declaration of "i" is unambiguous, languages like C++ don't have any beef with it. But the C# language designers decided to forbid it. Given the above snippet, do you think still think that was a bad idea? Throw in a bunch of extra code and you could stare at this code for a while and not see the bug.

The workaround is trivial and painless, just come up with a different variable name.

Hans Passant
Beat me to it :-/...
Andreas Huber
That is why we have warnings isn't it?
Anders Rune Jensen
Hmya, warnings... Do you like the C/C++ compiler's /Wx option too? A language designer that says "no" instead of "maybe" gets my vote.
Hans Passant
In this case yes. Because I consider the delegate case a language bug since it makes me have to write suboptimal code just to make the compiler happy. The language is supposed to work with you, not against you...
Anders Rune Jensen
Just to be clear I think that you example probably should generate a compile error, while for the example posted in the question I still stand strong on my last comment :)
Anders Rune Jensen
Well, that's okay. But you're having an argument with a chunk of silicon, it won't listen nor talk back. Post to connect.microsoft.com
Hans Passant
A: 

Actually, the error doesn't seem to have anything to do with anonymous delegates or lamda expressions. If you try to compile the following program ...

using System;

class Program
{
    static void Main()
    {
        // Action t = delegate
        {
            int i = 0;
        };

        int i = 1;
    }
}

... you get exactly the same error, no matter whether you comment in the line or not. The error help shows a very similar case. I think it is reasonable to disallow both cases on the grounds that programmers could confuse the two variables.

Andreas Huber