Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this an example of a closure in C#?

Tags:

closures

c#

static void Main(string[] args)
{
    var s = 3;

    Func<int, Func<int>> func = 
        x => () =>
    {
        return x;
    };

    var result1 = func(s);

    func = null;

    s = 5;

    var result2 = result1();

    Console.WriteLine(result2);


    Console.ReadKey();
}

My understanding is that x is not actually declared as a variable eg. var x = 3. Instead, it's passed into the outer function, which returns a function that returns the original value. At the time it is returning this, it creates a closure around x to remember its value. Then later on, if you alter s, it has no effect.

Is this right?

(Output is 3 by the way, which I'd expect).

Edit: here's a diagram as to why I think it is

Closure example

x=3 is passed into the func, and it returns a function that simply returns x. But x doesn't exist in the inner function, only its parent, and its parent no longer exists after I make it null. Where is x stored, when the inner function is ran? It must create a closure from the parent.

Further clarification:

    int s = 0;

    Func<int, Func<int>> func = 
        x => () =>
    {
        return x;
    };

    for (s = 0; s < 5; s++)
    {
        var result1 = func(s);

        var result2 = result1();

        Console.WriteLine(result2);
    };

Output is 0, 1, 2, 3, 4

However with your example:

static void Main(string[] args)
{
    int s = 0;

    Func<int, Func<int>> func = 
        x => () =>
    {
        return s;
    };

    List<Func<int>> results = new List<Func<int>>();

    for (s = 0; s < 5; s++)
    {
         results.Add(func(s));
    };

    foreach (var b in results)
    {
        Console.WriteLine(b());
    }

    Console.ReadKey();
}

The output is 5 5 5 5 5, which isn't what you want. It hasn't captured the value of the variable, it's merely retained a reference to the original s.

Closures are created in javascript precisely to avoid this problem.

like image 364
NibblyPig Avatar asked Jun 12 '12 14:06

NibblyPig


1 Answers

No, that's not a closure on s, x is just an argument to the lambda expression which generates a delegate (which could actually be simplified to x => () => x.

        Func<int, Func<int>> func = x => () => x;

        var a = func(3);

        var b = func(5);

        Console.WriteLine(a());
        Console.WriteLine(b());

You'll get 3 and 5 as expected. In neither case does it actually remember x from the other time, but it does close on x locally during the duration of the outer lambda.

Basically, each call to x => () => x will create a new () => x delegate which captures the local value of x (not of the s passed in).

Even if you use s and pass it in, and change it, you still get 3 and 5:

        int s = 3;
        Func<int, Func<int>> func = x => () => x;

        var a = func(s);

        s = 5;

        var b = func(s);

        // 3 and 5 as expected
        Console.WriteLine(a());
        Console.WriteLine(b());

Now, it does capture on the local variable x within the local function, which is generated at every call. So the x won't persist between invocations of the larger lambda, but it will be captured if changed later in the lambda.

For example, let's say instead you had this:

        int s = 3;
        Func<int, Func<int>> func = x => 
            () => {
                      Func<int> result = () => x;
                      x = 10 * x;
                      return result;
                   };

        var a = func(s);

        s = 5;

        var b = func(s);

In this case, the closure on x within the anonymous method is more obvious. The results of running this will be 30 and 50 because modifications to x within the anonymous method affect the closure on the x local to that anonymous method, however, these do not carry over between calls, because it's only capturing the local x passed into the anonymous method, and not the s used to call it.

So, to sum up, in your diagram and example: * main passes s into the larger lambda (func in your diagram) * func closes on x at the time it's called to generate the anonymous method () => x

Once the call to the outer lambda is made (func) the closer begins and ends because it's local to that lambda only and doesn't close on anything from main.

Does that help?

like image 188
James Michael Hare Avatar answered Sep 27 '22 17:09

James Michael Hare