为什么有些closures比其他人更“友善”?
让我提前道歉 – 我可能在屠杀术语。 我对封闭是什么有一个模糊的理解,但不能解释我所看到的行为。 至less,我认为这是一个封闭的问题。 我在网上search,但还没有find正确的关键字来得到我想要的。
具体来说 – 我有两个真正类似的代码块(至less在我眼中)。 第一:
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
这对我来说是有道理的,并且与我读过的解释相符。 在幕后,为每个委托/动作创build一个类,并为该类赋予一个字段来保存“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
variables声明(来自不同的方法调用)。 你的第二个例子是共享相同的variables声明。
你的第一个例子的行为与第二个例子的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
主要的问题是在方法中声明的局部variables (在你的情况下int count = 0;
)对于该方法的调用是唯一的,那么当lambda委托被创build时,每个variables都在其自身唯一的count
variables:
Action x1 = GetWorker(0); //gets a count Action x2 = GetWorker(1); //gets a new, different count
闭包捕获一个variables 。
当一个方法被调用被激活时,会创build一个局部variables 。 (还有其他的东西可以创build局部variables,但现在让我们忽略它。)
在你的第一个例子中,你有两个GetWorker
激活,因此创build了两个完全独立的名为count
variables。 每个都被独立捕获。
在你的第二个例子中,不幸的是你没有显示全部,你有一个单一的激活和两个closures。 closures共享variables。
以下是一个可以帮助您思考的方法:
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
variables不被多个对象共享,而在第二个variables中只有一个被多个对象共享?
不同的是,在一个例子中,你有一个代表,另一个代表你有两个代表。
由于countvariables是本地的,每次拨打电话都会重新生成。 由于只有一个委托使用(由于三元)每个委托获取variables的不同副本。 在另一个例子中,两个代表都得到相同的variables。
三元运算符只返回其两个参数中的一个,所以闭包按照您的预期工作。 在第二个示例中,您将创build两个共享相同“父”计数variables的闭包,并给出不同的结果。
如果你这样看,可能会更清楚一点(这与你的第一个例子是等价的):
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 }
显然这里只产生一个闭包,而你的另一个样本显然有两个。
另一种select(你可能正在寻找什么):
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(); }