2013-10-28 26 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