Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closures behavior

Tags:

closures

c#

.net

This function returns two different values depending on the way its called. I understand that Closures close over variables, not over values and I expected the values returned from the second call to be the same regardless of how the function is called

static Func<int, int,int> Sum()
{
    var test = 1;
    return (op1, op2) =>
    {
        test = test + 1;

        return (op1 + op2) + test;
    };
}

Here is the call:

var mFunc = Sum();
Console.WriteLine("Calling Sum(1,1) with Invoke() " +   Sum().Invoke(1, 1));
Console.WriteLine("Calling Sum(2,2) with Invoke() " + Sum().Invoke(2, 2));

Console.WriteLine("Calling mFunc(1,1)" + mFunc(1, 1));
Console.WriteLine("Calling mFunc(2,2)" + mFunc(2, 2));
Console.Read();

The result of using Invoke:

4
6

The result of using assigned variable:

4
7

Why does using Invoke changes the closures behavior?

like image 625
user3373870 Avatar asked Mar 14 '14 18:03

user3373870


1 Answers

Each time you call Sum, you're creating a separate test delegate closing over a new test variable. The difference isn't "whether or not you use Invoke" but "whether you're using the result of a new call to Sum().

To demonstrate this, you can just change your calls to:

var mFunc = Sum();
Console.WriteLine("Calling mFunc(1,1) with Invoke() " + mFunc.Invoke(1, 1));
Console.WriteLine("Calling mFunc(2,2) with Invoke() " + mFunc.Invoke(2, 2));
Console.WriteLine("----------");
Console.WriteLine("Calling mFunc(1,1)" + mFunc(1, 1));
Console.WriteLine("Calling mFunc(2,2)" + mFunc(2, 2));
Console.Read();

Or you could call Sum each time instead:

var mFunc = Sum();
Console.WriteLine("Calling Sum()(1,1) with Invoke() " + Sum().Invoke(1, 1));
Console.WriteLine("Calling Sum()(2,2) with Invoke() " + Sum().Invoke(2, 2));
Console.WriteLine("----------");
Console.WriteLine("Calling Sum()(1,1)" + Sum()(1, 1));
Console.WriteLine("Calling Sum()(2,2)" + Sum()(2, 2));
Console.Read();

Of course you don't need any parameters to demonstrate this:

static Action Increment()
{
    int test = 1;
    return () =>
    {
        Console.WriteLine(test);
        test++;
    };
}

Then:

// New delegate for each call
Increment()(); // Prints 1
Increment()(); // Prints 1 again
Increment()(); // Prints 1 again

// Same delegate three times
var action = Increment();
action(); // Prints 1
action(); // Prints 2
action(); // Prints 3
like image 68
Jon Skeet Avatar answered Nov 01 '22 20:11

Jon Skeet