让我提前道歉 - 我可能正在屠杀术语。我对封闭是什么有一个模糊的理解,但无法解释我所看到的行为。至少,我认为这是一个封闭问题。我在网上搜索过,但找不到合适的关键字来获取我想要的内容。
具体来说 - 我有两块完全相似的代码(至少在我看来)。第一:
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()似乎具有SAME计数值!
Working 3 - 0
Working 4 - 1
Working 3 - 2
Working 4 - 3
Working 3 - 4
Working 4 - 5
Working 3 - 6
Working 4 - 7
我可以看发生了什么 - 但我真的不明白为什么他们会被区别对待。在我的脑海里 - 我喜欢我所看到的原始行为,但后面的例子让我困惑。我希望这是有道理的。感谢
答案 0 :(得分:50)
您的第一个示例有两个不同的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
答案 1 :(得分:27)
闭包捕获变量。
方法是方法 激活时通过调用创建的本地变量。 (还有其他一些东西可以创建局部变量,但现在让我们忽略它。)
在第一个示例中,您有两次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
而不被多个对象共享,而第二个只有一个变量被多个对象共享?
答案 2 :(得分:6)
不同之处在于,在一个示例中,您有一个委托,另一个您有两个委托。
由于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
}
显然,这里只生成一个闭包,而另一个样本显然有两个闭包。
答案 3 :(得分:2)
另一种选择(或许你正在寻找):
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();
}