Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closure captured variable modifies the original as well

Tags:

closures

c#

.net

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?

like image 290
Freeman Avatar asked Mar 07 '13 15:03

Freeman


People also ask

How closure works?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.

What is variable capture?

Variable capture occurs when macroexpansion causes a name clash: when some symbol ends up referring to a variable from another context. Inadvertent variable capture can cause extremely subtle bugs.

What is closure C#?

In C#, the capability that allows a method or a function to reference a non-local variable or value is called closure.


2 Answers

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?

like image 115
Eric Lippert Avatar answered Oct 17 '22 00:10

Eric Lippert


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.

like image 37
Tim Goodman Avatar answered Oct 17 '22 00:10

Tim Goodman