簡體   English   中英

由 C# 中的 lambda 創建的委托的生命周期是多少?

[英]What is the lifetime of a delegate created by a lambda in C#?

Lambda 很好,因為它們提供簡潔性和局部性以及額外的封裝形式 不必編寫只使用一次的函數,您可以使用 lambda。

雖然想知道它們是如何工作的,但我直覺地認為它們可能只創建一次 This inspired me to create a solution which allows to restrict the scope of a class member beyond private to one particular scope by using the lambda as an identifier of the scope it was created in.

這個實現工作,雖然可能有點矯枉過正(仍在研究它),證明我的假設是正確的。

一個較小的例子:

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()}}
    }
}

lambda 會返回一個新實例,還是保證始終相同?

無論哪種方式都不能保證

根據我對當前 MS 實施的記憶:

  • 不捕獲任何變量的 lambda 表達式被靜態緩存
  • 僅捕獲“this”的 lambda 表達式可以在每個實例的基礎上捕獲,但不是
  • 無法緩存捕獲局部變量的 lambda 表達式
  • 兩個具有完全相同程序文本的 lambda 表達式沒有別名; 在某些情況下,它們可能是,但找出它們可能的情況將非常復雜
  • 編輯:正如 Eric 在評論中指出的那樣,您還需要考慮為通用方法捕獲類型arguments。

編輯:C# 4 規范的相關文本在第 6.5.1 節中:

允許(但不要求)將具有相同(可能為空)捕獲的外部變量實例集的語義相同的匿名函數轉換為相同的委托類型,以返回相同的委托實例。 這里使用的術語語義相同是指匿名函數的執行在所有情況下都會產生相同的效果,給定相同的 arguments。

根據您在這里的問題和您對喬恩回答的評論,我認為您混淆了很多事情。 為確保清楚:

  • 支持給定 lambda 的委托的方法始終相同。
  • 支持在詞法上出現兩次的“相同”lambda 的委托的方法允許相同,但實際上在我們的實現中並不相同。
  • 為給定的 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;

現在清楚了嗎?

沒有保證。

快速演示:

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

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

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

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

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

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM