5

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?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
user3373870
  • 1,406
  • 2
  • 13
  • 17

1 Answers1

16

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
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks Jon, I knew it wasn't related to the use of parameters. I just used them to demonstrate similar functionality to a bigger function I was working with. – user3373870 Mar 14 '14 at 18:38