簡體   English   中英

為什么某些關閉方式比其他方式“友好”?

[英]Why Are Some Closures 'Friendlier' Than Others?

我先向您道歉-可能是在抹殺術語。 我對閉包是什么有一個模糊的理解,但是無法解釋我所看到的行為。 至少,我認為這是一個關閉問題。 我已經在網上搜索過,但沒有找到合適的關鍵字來獲得想要的東西。

具體來說-我有兩個非常相似的代碼塊(至少在我看來)。 第一:

static void Main(string[] args)
{
    Action x1 = GetWorker(0);
    Action x2 = GetWorker(1);
}

static Action GetWorker(int k)
{
    int count = 0;

    // Each Action delegate has it's own 'captured' count variable
    return k == 0 ? (Action)(() => Console.WriteLine("Working 1 - {0}",count++))
                  : (Action)(() => Console.WriteLine("Working 2 - {0}",count++));
}

如果運行此代碼並調用x1()和x2(),您將看到它們維護單獨的“計數”值。

    foreach(var i in Enumerable.Range(0,4))
    {
        x1(); x2(); 
    }

輸出:

Working 1 - 0
Working 2 - 0
Working 1 - 1
Working 2 - 1
Working 1 - 2
Working 2 - 2
Working 1 - 3
Working 2 - 3

這對我來說很有意義,並且與我閱讀的說明相符。 在幕后為每個委托/動作創建一個類,並為該類提供一個字段來保存“ count”的值。 我上床感覺很聰明!

但是,我嘗試了以下非常相似的代碼:

    // x3 and x4 *share* the same 'captured' count variable
    Action x3 = () => Console.WriteLine("Working 3 - {0}", count++);
    Action x4 = () => Console.WriteLine("Working 4 - {0}", count++);

並且(如評論所言),這里的行為是完全不同的。 x3()和x4()似乎具有相同的計數值!

Working 3 - 0
Working 4 - 1
Working 3 - 2
Working 4 - 3
Working 3 - 4
Working 4 - 5
Working 3 - 6
Working 4 - 7

我可以看到發生了什么-但是我真的不明白為什么要對它們進行不同的對待。 在我的腦海中-我喜歡我所看到的原始行為,但是后面的示例使我感到困惑。 我希望這是有道理的。 謝謝

您的第一個示例有兩個不同的int count變量聲明(來自單獨的方法調用)。 您的第二個示例共享相同的變量聲明。

您的第一個示例的行為與第二個示例的行為相同,前提是int count是主程序的一個字段:

static int count = 0;

static Action GetWorker(int k)
{
    return k == 0 ? (Action)(() => Console.WriteLine("Working 1 - {0}",count++))
                  : (Action)(() => Console.WriteLine("Working 2 - {0}",count++));
}

輸出:

Working 1 - 0
Working 2 - 1
Working 1 - 2
Working 2 - 3
Working 1 - 4
Working 2 - 5
Working 1 - 6
Working 2 - 7

您也可以不使用三元運算符來簡化它:

static Action GetWorker(int k)
{
    int count = 0;

    return (Action)(() => Console.WriteLine("Working {0} - {1}",k,count++));
}

哪個輸出:

Working 1 - 0
Working 2 - 0
Working 1 - 1
Working 2 - 1
Working 1 - 2
Working 2 - 2
Working 1 - 3
Working 2 - 3

主要問題是,在方法中聲明的局部變量 (在您的情況下為int count = 0; )對於該方法的調用是唯一的,然后在創建lambda委托時,每個變量都在其自己的唯一count變量周圍應用閉包:

Action x1 = GetWorker(0); //gets a count
Action x2 = GetWorker(1); //gets a new, different count

閉包捕獲變量

通過調用 激活 方法時會創建局部變量 (還有其他一些東西可以創建局部變量,但是現在讓我們忽略它。)

在第一個示例中,您具有兩次激活GetWorker ,因此將創建兩個名為count完全獨立的變量。 每個都是獨立捕獲的。

在第二個示例中,不幸的是您沒有顯示所有示例,因此您具有一個激活和兩個閉包。 閉包共享變量。

這是一種可能會有所幫助的思考方式:

class Counter { public int count; }
...
Counter Example1()
{
    return new Counter();
}
...
Counter c1 = Example1();
Counter c2 = Example1();
c1.count += 1;
c2.count += 2;
// c1.count and c2.count are different.

VS

void Example2()
{
    Counter c = new Counter();
    Counter x3 = c; 
    Counter x4 = c;
    x3.count += 1;
    x4.count += 2;
    // x3.count and x4.count are the same.
}

您是否有道理,為什么在第一個示例中有兩個稱為count變量不被多個對象共享,而在第二個示例中,只有一個變量被多個對象共享?

區別在於,在一個示例中,您有一個委托,而另一個則有兩個。

由於count變量是本地變量,因此每次調用時都會重新生成。 由於僅使用一個委托(由於三進制),每個委托都會獲得變量的不同副本。 在另一個示例中,兩個委托都獲得相同的變量。

三元運算符僅返回其兩個參數之一,因此閉包可以按預期工作。 在第二個示例中,您將創建兩個具有相同“父”計數變量的閉包,從而得出不同的結果。

如果您這樣看,可能會更清楚(這與您的第一個示例等效):

static Action GetWorker(int k)
{
    int count = 0;
    Action returnDelegate

    // Each Action delegate has it's own 'captured' count variable
    if (k == 0)
         returnDelegate = (Action)(() => Console.WriteLine("Working 1 - {0}",count++));
    else
         returnDelegate = (Action)(() => Console.WriteLine("Working 2 - {0}",count++));

    return returnDelegate
}

顯然,這里僅生成一個封閉,而您的其他樣本顯然有兩個。

另一種選擇(也許您正在尋找的):

static Action<int> GetWorker()
{
    int count = 0;

    return k => k == 0 ? 
             Console.WriteLine("Working 1 - {0}",count++) : 
             Console.WriteLine("Working 2 - {0}",count++);
}

然后:

var x = GetWorker();

foreach(var i in Enumerable.Range(0,4))
{
    x(0); x(1);
}    

或許:

var y = GetWorker();
// and now we refer to the same closure
Action x1 = () => y(0);
Action x2 = () => y(1);

foreach(var i in Enumerable.Range(0,4))
{
    x1(); x2(); 
}

也許還有一些咖喱:

var f = GetWorker();
Func<int, Action> GetSameWorker = k => () => f(k);

//  k => () => GetWorker(k) will not work

Action z1 = GetSameWorker(0);
Action z2 = GetSameWorker(1);    

foreach(var i in Enumerable.Range(0,4))
{
    z1(); z2(); 
}

暫無
暫無

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

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