6

I have the following simple code:

static void Main(string[] args)
{
    int j = 0;
    Func<int> f = () =>
    {
        for (int i = 0; i < 3; i++)
        {
            j += i;
        }
        return j;
    };

    int myStr = f();
    Console.WriteLine(myStr);
    Console.WriteLine(j);
    Console.Read();
}

From what I read when closures are involved, a new type is created by the compiler so it can store the captured variable and maintain a reference to it. However, when I run the following code, both printed lines show 3. I was expecting 0 and 3, because the anonymous method has its own variable in the generated class by the compiler. So why does it also modify the outside variable?

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
Freeman
  • 5,691
  • 3
  • 29
  • 41

3 Answers3

15

The outside variable and the variable in the closure are the same variable. Your program is equivalent to:

private class Closure
{
    public int j;
    public int Method()
    {
        for (int i = 0; i < 3; i++)
        {
            this.j += i;
        }
        return this.j;
    }
}
static void Main(string[] args)
{
    Closure closure = new Closure();
    closure.j = 0;
    Func<int> f = closure.Method;
    int myStr = f();
    Console.WriteLine(myStr);
    Console.WriteLine(closure.j);
    Console.Read();
}

Now is it clear why you get the observed result?

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Wow, first of all, haven't seen an answer from you for a while. Second, its very clear now, altough i looked at the IL, its somewhat giberish for me. I had no ideea it acts like that. – Freeman Mar 07 '13 at 16:04
  • 1
    Eric, you said this behavior may change in a future release of C#. I know you and MS said your goodbye's. However, did this change make it into 5.0 or still think that's gonna happen? – P.Brian.Mackey Mar 07 '13 at 16:07
  • 6
    @P.Brian.Mackey: The behaviour that a lambda captures an outer variable is by design and will never change. However, we did make a change in C# 5: the *loop variable of a foreach* is now logically *inside* the loop body, not logically *outside* the loop body. Therefore every time you close over the loop variable of a foreach you get a *fresh* variable to close over in C# 5.0, not a shared variable as you do in C# 4.0. – Eric Lippert Mar 07 '13 at 16:09
  • If there were two lambda's in `Main` which closed over the same variables, would the compiler generate a single `Closure` class, but with a separate `Method` for each of the lambdas? I guess it has to since they're closing over the same variables. – Tim Goodman Mar 07 '13 at 18:16
  • 1
    @TimGoodman: Yes. And in fact if there are two lambdas that close over *different* variables, only one closure is generated! This means that the lifetime of one variable can be extended by a delegate that does not close over it. It's a know shortcoming of C# that hopefully will eventually be addressed. – Eric Lippert Mar 07 '13 at 19:32
  • @TimGoodman: Jon Skeet and Eric both discuss this closure issue a bit more in answers to my question [here](http://stackoverflow.com/q/3885106/18192). – Brian Mar 07 '13 at 22:06
5

This is how closures work, they capture variables, not values. So j will be changed.

If you don't want that, you can do this:

static void Main(string[] args)
{
    int j = 0;
    Func<int> f = () =>
    {
        int k = j;
        for (int i = 0; i < 3; i++)
        {
            k += i;
        }
        return k;
    };
    int myStr = f();
    Console.WriteLine(myStr);
    Console.WriteLine(j);
    Console.Read();
}

j is still captured by the closure, but not modified. Only the copy k is modified.

Edit:

You correctly note that this won't work for reference types. In that case k = j stores a copy of a reference to an object. There's still one copy of the object being referenced, so any modifications to that object will effect both variables.

Here's an example of how you would use the closure for a reference type and not update the original variable:

static void Main(string[] args)
{
    Foo j = new Foo(0);
    Func<Foo> f = () =>
    {
        Foo k = new Foo(j.N); // Can't just say k = j;
        for (int i = 0; i < 3; i++)
        {
            k.N += 1;
        }
        return k;
    };

    Console.WriteLine(f().N);
    Console.WriteLine(j.N);
    Console.Read();
}

public class Foo
{
    public int N { get; set; }

    public Foo(int n) { N = n; }
}

However, strings being immutable reference types, you actually can just say k = j, unlike with arbitrary reference types. One way to think of the immutability is that every time you update the value of a string, you are actually creating a new instance. So k = k + "1" is like saying k = new String(k + "1"). At that point, it's no longer a reference to the same string as j.

Tim Goodman
  • 23,308
  • 7
  • 64
  • 83
  • at first it look like very strange behaviour, altough k is a value type, thus its probably a new copy of j, so thats why only k is affected – Freeman Mar 07 '13 at 15:58
  • does copying the variable offer the same behaviour for reference types?What if j was a string for example. – Freeman Mar 07 '13 at 16:00
  • You're right that being a value type matters. For reference types, you have to construct a new object for k (e.g. with the `new` keyword). If you just say `k = j` they will refer to the same object. Strings are an exception though, because they're immutable -- essentially every time you update the value of a string, you're actually creating a new instance of the `String` class. – Tim Goodman Mar 07 '13 at 16:10
  • I've expanded my answer to cover how things are different for reference types. – Tim Goodman Mar 07 '13 at 16:23
  • Yes but it still modifies the both... my Env is .NET 4.5 with vs 2012 – Freeman Mar 07 '13 at 16:24
  • When you just use the closure without defining the new variable k, then both are modified (this is true for value types and reference types). When you define the variable `k = j` within the closure (my first example above), then for value types and immutable reference types like `String` only `k` is changed, not `j`. For mutable reference types (like `Foo` in my second example), if you want `k` to change and not `j` you can use `new` like in my second example. – Tim Goodman Mar 07 '13 at 16:27
  • Which is modifying both? The output of both my examples above is `3` `0`. – Tim Goodman Mar 07 '13 at 16:29
  • both of them, even if i use int or string both variables are affected. Im doing this just for academic purposes, to understand its behaviour, its not a requirement, but i might encounter it and must know how to handle the situation. – Freeman Mar 07 '13 at 16:29
  • Your saying you've copied my code exactly and are getting `3` `3` instead of `3` `0`? I'm not sure I believe it... Please check for copy errors. – Tim Goodman Mar 07 '13 at 16:32
  • In the case when i use new, then the output is indeed 3 0, but in this case i am no longer using an outside variable, but rather a variable declared wthin the same method that has no reference to the old one. Therefore i am not capturing the outside variable. – Freeman Mar 07 '13 at 16:34
  • The closure captures the outside variable `j`. Try changing `j` before calling `f()` to see this, the closure will use the changed value. However, the point of my examples is to show that you can copy the value of `j` to a new variable `k` within `f()` and that way any updates you make to this new variable won't affect `j`. This is useful if you want to use a lambda but *not* modify the variable you closed over. – Tim Goodman Mar 07 '13 at 16:42
  • That is true, thank youfor the examples.i looked in the IL and it seems the new type that holds the outside var is still created, if i am reading this correctly: "newobj instance void ConsoleApplication1.Program/'<>c__DisplayClass1'::.ctor()" – Freeman Mar 07 '13 at 16:48
4

The language specification says:

Anonymous methods are similar to lambda functions in the Lisp programming language. C# 2.0 supports the creation of “closures” where anonymous methods access surrounding local variables and parameters.

And 'j' in your case is surrounding variable.

Denys Denysenko
  • 7,598
  • 1
  • 20
  • 30