2010-11-09 204 views
5

考慮這個代碼片段,並試着猜測什麼y1y2評估爲爲什麼這兩個函數不會返回相同的值?

static class Extensions 
{ 
    public static Func<T> AsDelegate<T>(this T value) 
    { 
     return() => value; 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     new Program(); 
    } 

    Program() 
    { 
     double x = Math.PI; 

     Func<double> ff = x.AsDelegate(); 
     Func<double> fg =() => x; 

     x = -Math.PI; 

     double y1 = ff(); // y1 = 3.141.. 
     double y2 = fg(); // y2 = -3.141.. 

    } 
} 

你可能會說-Aha-雙是一個值類型,因此通過擴展方法的返回值是的一個副本主要是x。但是當你將上面的代碼改爲類的代表時,結果仍然不同。例如:

class Foo 
{ 
    public double x; 
} 
    Program() 
    { 
     Foo foo = new Foo() { x=1.0 }; 

     Func<Foo> ff = foo.AsDelegate(); 
     Func<Foo> fg =() => foo; 

     foo = new Foo() { x = -1.0 }; 

     double y1 = ff().x; // y1 = 1.0 
     double y2 = fg().x; // y2 = -1.0 
    } 

所以這兩個函數必須返回同一個類的兩個不同實例。有趣的是,考慮到ff()帶有對局部變量foo的引用,但fg()沒有,並且它依賴於當前範圍內的內容。

那麼當這兩個代表被傳遞到代碼的其他部分時,如果foo實例不具有可見性,會發生什麼情況?不管怎樣,當擴展方法與代表組合時,誰擁有什麼信息(數據)變得越來越不明確。

回答

3

ff捕獲(結合)至x在這一行:

Func<double> ff = x.AsDelegate(); 

相比之下,fg結合到可變x在這一行:

Func<double> fg =() => x; 

所以,當x的值改變時,ff不受影響,但fg更改。

+0

點擊此答案,因爲簡潔+清晰度+格式。 – ja72 2010-11-09 18:21:04

+0

咩...這一個:不綁定到一個*值*的東西(除了編譯器生成捕獲類的實例),和b:不嚴格綁定到「X」 *都* - 更清晰的是,但由於這種簡單性,我認爲可能有點誤導。 (雖然簡單很好) – 2010-11-09 18:39:56

+0

@Marc - 這是一個折騰。我想着重於用戶看起來的方式,而不是深入實施。授予 – Bevan 2010-11-09 20:28:02

2

() => x捕獲x值。編譯器創建特殊類以處理lambda或匿名委託,捕獲lambda中使用的任何變量。

例如如果您運行下面的代碼:

List<Func<int>> list = new List<Func<int>>(); 

    for (int i = 0; i < 5; i++) 
    { 
     list.Add(() => i); 
    } 

    list.ForEach(function => Console.WriteLine(function())); 

你會看到,印刷數字是相同的。

+0

所以,當函數()被調用,它使用我有那個時間(外循環)的任何值?我認爲'i'的範圍在循環結束時結束...... – ja72 2010-11-09 18:14:59

7

AsDelegate捕獲可變value(的AsDelegate參數),而() => x捕獲可變x。所以如果你改變x的值,lambda表達式將返回一個不同的值。雖然更改x不會更改value

請參見:Outer Variable Trap

+0

嚴格地說,它捕獲*參數*值;其價值最初是x的價值,永遠不會改變。細微的區別... – 2010-11-09 18:07:35

3

AsDelegate擴展方法使用的x值在時間AsDelegate叫做而lambda表達式() => x捕獲可變x並且因此x的值在表達式被調用的時間(而不是值時,它取被定義)。

4

在AsDelegate中,我們正在捕獲參數「value」。此方法的值被視爲調用方法時變量值的副本,並且永遠不會更改 - 因此我們可以看到原始對象。

直接拉姆達捕捉變量 FOO(不是值的變量的 - 變量本身) - 因此,我們看到改變了這一點。

基本上添加一個方法調用改變了被捕獲的大腿。

0

第一方法創建一個新的委託於給定功能,並且存儲它。它後來覆蓋foo,你新創建的委託沒有被觸及。

第二個是一個拉姆達表達其升降機/* *捕獲它的上下文,這意味着foo的變量。該lambda表達式可以看到所有由lambda表達式提升的變量。

相關問題