2011-06-08 42 views
41

Lambdas很不錯,因爲他們提供brevity and localityan extra form of encapsulation。而不必編寫僅在您使用lambda時才使用的函數。什麼是在C#中使用lambda創建的委託的生命週期?

雖然想知道他們是如何工作的,但我直覺地認爲他們是可能只創建一次。這啓發了我創建一個解決方案,它允許to restrict the scope of a class member beyond private一個特定的範圍,通過使用lambda作爲其創建範圍的標識符。

此實現的工作,儘管可能矯枉過正(仍在研究它),證明我的假設來是正確的。

較小的例如:

class SomeClass 
{ 
    public void Bleh() 
    { 
     Action action =() => {}; 
    } 

    public void CallBleh() 
    { 
     Bleh(); // `action` == {Method = {Void <SomeClass>b__0()}} 
     Bleh(); // `action` still == {Method = {Void <SomeClass>b__0()}} 
    } 
} 

請問拉姆達再回到一個新的實例,或者是它保證始終是一樣的嗎?

回答

26

根據你在這裏的問題和你對Jon的回答的評論我認爲你混淆了多個事情。爲了確保它是明確的:

  • 方法是備份代理對於給定的λ是永遠不變的。
  • 方法是備份該詞彙出現兩次是允許是一樣的「相同」拉姆達的代表,但在實踐中是在我們的實現是相同的。
  • 委託實例爲給定的lambda創建的可能會也可能不總是相同的,這取決於編譯器是如何聰明地緩存它的。

所以,如果你有這樣的:

for(i = 0; i < 10; ++i) 
    M(()=>{}) 

然後每M被調用時,你會得到委託的同一個實例因爲編譯器是聰明的,併產生

static void MyAction() {} 
static Action DelegateCache = null; 

... 
for(i = 0; i < 10; ++i) 
{ 
    if (C.DelegateCache == null) C.DelegateCache = new Action (C.MyAction) 
    M(C.DelegateCache); 
} 

如果您有

for(i = 0; i < 10; ++i) 
    M(()=>{this.Bar();}) 

那麼編譯器生成

void MyAction() { this.Bar(); } 
... 
for(i = 0; i < 10; ++i) 
{ 
    M(new Action(this.MyAction)); 
} 

你每次都獲得新的委託,用同樣的方法。

編譯器是允許(但事實上並沒有在這個時候)產生

void MyAction() { this.Bar(); } 
Action DelegateCache = null; 
... 
for(i = 0; i < 10; ++i) 
{ 
    if (this.DelegateCache == null) this.DelegateCache = new Action (this.MyAction) 
    M(this.DelegateCache); 
} 

在這種情況下,如果可能的話,你會總是得到相同的委託實例,每一個代表將由備份同樣的方法。

如果有

Action a1 =()=>{}; 
Action a2 =()=>{}; 

然後,在實踐中,編譯器會生成此作爲

static void MyAction1() {} 
static void MyAction2() {} 
static Action ActionCache1 = null; 
static Action ActionCache2 = null; 
... 
if (ActionCache1 == null) ActionCache1 = new Action(MyAction1); 
Action a1 = ActionCache1; 
if (ActionCache2 == null) ActionCache2 = new Action(MyAction2); 
Action a2 = ActionCache2; 

然而,編譯器是允許以檢測這兩個lambda表達式是相同的並且產生

static void MyAction1() {} 
static Action ActionCache1 = null; 
... 
if (ActionCache1 == null) ActionCache1 = new Action(MyAction1); 
Action a1 = ActionCache1; 
Action a2 = ActionCache1; 

現在清楚了嗎?

27

這不是保證無論哪種方式。

從我記住當前MS執行:

  • 不捕捉任何變量lambda表達式是靜態
  • lambda表達式,只有抓住「本」 可能緩存被捕獲在每個實例的基礎上,但不是
  • 捕獲局部變量的lambda表達式不能被緩存
  • 兩個lambda表達式s具有完全相同的程序文本不是別名;在某些情況下,他們可以來,但工作了,使他們能夠成爲情形將會非常複雜
  • 編輯:正如埃裏克在評論中指出的,你還需要考慮類型參數被捕獲的通用方法。

編輯:C#4規範的相關的文字是在節6.5.1:

用相同的(可能爲空)到捕獲的外層變量實例的語義相同匿名函數換算相同的委託類型被允許(但不是必需的)返回相同的委託實例。這裏使用的術語語義相同是指執行匿名函數在任何情況下都會在給定相同參數的情況下產生相同的效果。

+0

「同樣的效果」究竟意味着什麼?調用'GetCurrentMethod'顯然沒有相同的效果... – Mehrdad 2011-06-08 14:54:56

+0

這聽起來對我來說是正確的,儘管如果lambda「捕獲」泛型方法的類型參數,事情也會變得棘手。即使它不使用本地或參數,它仍然可能無法緩存在靜態字段中。 – 2011-06-08 15:00:02

+0

@Eric:[我的一個測試用例](http://stackoverflow.com/questions/4986023/restricting-scope-of-a-class-member-beyond-private/4986143#4986143)使用'public static Local 實例(函數作用域)',其中'()=> this'被傳遞給作用域。在這種情況下,它仍然返回相同的代表。 – 2011-06-08 15:09:49

1

好問題。我沒有「學術答案」,更多的是一個實際的答案:我可以看到一個優化二進制文件的編譯器使用同一個實例,但我永遠不會編寫假定它「被保證」是相同實例的代碼。

我向你致敬至少,所以希望有人能給你你正在尋找的學術答案。

3

我看到Skeet在我回答的時候跳了進來,所以我不會說那個點。爲了更好地理解你如何使用事物,我建議的一件事就是熟悉逆向工程工具和IL。取出有問題的代碼示例並反向工程到IL。它會給你提供大量有關代碼如何工作的信息。

+3

這不應該是一個評論? – Trufa 2011-06-08 16:35:59

+0

你的第一句話是一個很好的一行。我打算使用這個技巧:) – nawfal 2013-10-16 13:35:50

4

沒有保證。

一個快速演示:

Action GetAction() 
{ 
    return() => Console.WriteLine("foo"); 
} 

調用此兩次,做一個ReferenceEquals(a,b),你會得到true

Action GetAction() 
{ 
    var foo = "foo"; 
    return() => Console.WriteLine(foo); 
} 

調用此兩次,做一個ReferenceEquals(a,b),你會得到false

+0

這證實了Jon的回覆:「捕獲局部變量的lambda表達式不能被緩存」。 – 2011-06-08 15:01:07

+0

@Steven除了「不捕獲任何變量的lambda表達式被靜態緩存」。 – Jay 2011-06-08 15:02:01

相關問題