为什么有些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 countvariables声明(来自不同的方法调用)。 你的第二个例子是共享相同的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都在其自身唯一的countvariables:

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

闭包捕获一个variables

当一个方法调用激活时,会创build一个局部variables 。 (还有其他的东西可以创build局部variables,但现在让我们忽略它。)

在你的第一个例子中,你有两个GetWorker激活,因此创build了两个完全独立的名为countvariables。 每个都被独立捕获。

在你的第二个例子中,不幸的是你没有显示全部,你有一个单一的激活和两个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. } 

这对你有意义吗?为什么在第一个例子中有两个称为countvariables不被多个对象共享,而在第二个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(); }