[英]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.