Saurabh Mahajan
Saurabh Mahajan

Reputation: 2969

How does a lambda expression shares a local variable?

I am reading about lambda expressions, and I've seen this example,

Example 1:

static Func<int> Natural()
{
    int seed = 0;
    return () => seed++; // Returns a closure
}

static void Main()
{
    Func<int> natural = Natural();
    Console.WriteLine (natural()); // output : 0
    Console.WriteLine (natural()); // output : 1
}

Example 2:

static Func<int> Natural()
{
    return() => { int seed = 0; return seed++; };
}

static void Main()
{
    Func<int> natural = Natural();
    Console.WriteLine (natural()); // output : 0
    Console.WriteLine (natural()); // output : 0
}

I am not able to understand why first example output is 0 and 1.

Upvotes: 3

Views: 2423

Answers (4)

dcastro
dcastro

Reputation: 68670

Seeing what kind of code the compiler generates might help you understand how closures work.

In your first example, your lambda expression is compiled as a closure, encapsulating the seed variable. This means that the compiler will generate a class that holds an instance of seed, and all calls to that lambda will increment that instance.

static Func<int> Natural()
{
    int seed = 0;
    return () => seed++; // Returns a closure
}

For the lambda above, the compiler will generate something like this, and return an instance of this class:

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    public int seed;

    public int <Natural>b__0()
    {
        return seed++;
    }
}

So, code like this:

Func<int> natural = Natural();
Console.WriteLine (natural()); // output : 0
Console.WriteLine (natural()); // output : 1

Is effectively the same as

<>c__DisplayClass1 closure = //...
Console.WriteLine ( closure.<Natural>b__0() ); // outputs 0
Console.WriteLine ( closure.<Natural>b__0() ); // outputs 1

Upvotes: 0

Pavan Tiwari
Pavan Tiwari

Reputation: 3187

A lambda expression can reference the local variables and parameters of the method in which it’s defined (outer variables)

Outer variables referenced by a lambda expression are called captured variables. A lambda expression that captures variables is called a closure.

Captured variables are evaluated when the delegate is actually invoked, not when the variables were captured:

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3));           // 30

Lambda expressions can themselves update captured variables:

int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural());           // 0
Console.WriteLine (natural());           // 1
Console.WriteLine (seed);                // 2

Captured variables have their lifetimes extended to that of the delegate. In the following example, the local variable seed would ordinarily disappear from scope when Natural finished executing. But because seed has been captured, its lifetime is extended to that of the capturing delegate, natural:

static Func<int> Natural()
{
  int seed = 0;
  return () => seed++;      // Returns a closure
}

static void Main()
{
  Func<int> natural = Natural();
  Console.WriteLine (natural());      // 0
  Console.WriteLine (natural());      // 1
}

A local variable instantiated within a lambda expression is unique per invocation of the delegate instance. If we refactor our previous example to instantiate seed within the lambda expression, we get a different (in this case, undesirable) result:

static Func<int> Natural()
{
  return() => { int seed = 0; return seed++; };
}

static void Main()
{
  Func<int> natural = Natural();
  Console.WriteLine (natural());           // 0
  Console.WriteLine (natural());           // 0
}

Upvotes: 5

SJuan76
SJuan76

Reputation: 24885

Because the initialization code in the second example (int seed = 0) is run at each invocation.

In the first example, seed is a captured variable that exists beyond the method, since there is only one instance its value is kept between invocations.

UPDATE: In response to David Amo's comment, an explanation.

Option 1)

static Func<int> Natural()
{
   int seed = 0;
   return () => seed++; // Returns a closure
}

Option 2)

static Func<int> Natural()
{
  return() => { int seed = 0; return seed++; };
}

Option 3)

static Func<int> Natural()
{
   int seed = 0;
   return () => { seed = 0; return seed++;}; // Returns a closure
}

Option 3 returns the same value that option 2, but internally works as option 1. seed is a variable defined inside Natural, but since it is captured by the delegate it continues to exist after the method has exited.

Another test that you may use to see what is happening is

static Func<int> Natural()
{
  int seed = 1;
  Func<int> returnValue = () => { return seed++; };
  seed = 2;
  return returnValue;
}

Upvotes: 6

tomsullivan1989
tomsullivan1989

Reputation: 2780

int seed=0 is inside the scope of the anonymous function, so is called every time the lambda expression is invoked. It returns 0 then get incremented by one, but is set to 0 when the function is called again.

In the first example has the seed variable declared outside of that scope and as there is only one instance its value is kept between calls.

Upvotes: 0

Related Questions