Reputation: 2969
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
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
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
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
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