2013-10-28 25 views
3

我念叨lambda表达式,我已经看到了这个例子,lambda表达式如何共享局部变量?

例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 
} 

例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 
} 

我无法理解为什么第一个例子输出为0和1.

+4

第二个e xample在匿名函数的作用域中包含'seed'变量(因此每次运行时都将其设置为0)。第一个版本的seed变量声明在该范围之外。 – Mike

回答

2

lambda表达式可以引用其中它被定义(外变量)由lambda表达式被称为捕获变量引用

外变量的方法的局部变量和参数。捕获变量的lambda表达式称为闭包。

捕获的变量进行评估时,实际上是调用委托,而不是当变量被抓获:

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

Lambda表达式可以自己更新捕获变量:

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

捕获的变量具有其生命期延伸到代表的时间。在下面的例子中,当自然完成执行时,局部变量种子通常会从范围中消失。但由于种子已经被捕获,其寿命延长到该俘获代表的,天然:

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 
} 

lambda表达式内实例化的局部变量是每个委托实例的调用是唯一的。如果我们重构我们前面的例子在lambda表达式中实例的种子,我们得到一个不同的(在这种情况下,不希望的)结果:

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 
} 
6

因为第二个示例中的初始化代码(int seed = 0)在每次调用时运行。

在第一个示例中,seed捕获的变量,该值超出该方法存在,因为只有一个实例在调用之间保留其值。

更新:回应David Amo的评论,解释。

项1)

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

选项2)

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

选项3)

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

选项3返回相同的值,该值的选项2,但在内部可以作为选项1 seedNatural中定义的一个变量,但是由于它由代表捕获它在方法退出后继续存在。

,你可以用,看看发生了什么事另一个测试是

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

值得一提的是,表达式的“= 0”部分没有区别。这是一个局部变量,每次调用该方法时都会重新创建该变量。 –

+0

实际上编译器不会让你在没有初始化的情况下使用'seed',所以它在技术上是需要的。 – rae1

+0

@ rae1n你在哪里看到一个未初始化的'seed'被使用? – SJuan76

0

int seed=0是匿名函数的范围内,所以被称为每一个lambda表达式被调用时。它返回0然后增加1,但当函数再次被调用时被设置为0。

在第一个示例中,seed变量声明在该作用域之外,并且只有一个实例在调用之间保留其值。

0

看到编译器生成可帮助您了解闭包是如何工作什么样的代码。

在第一个示例中,您的lambda表达式被编译为闭包,封装了seed变量。这意味着编译器将生成一个包含seed实例的类,并且对该lambda表达式的所有调用都将增加该实例。

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

对于上面的拉姆达,编译器会产生这样的事情,并返回这个类的一个实例:

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

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

所以,这样的代码:

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

有效与

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