为什么有些关闭' Friendlier'相对于其它的?

时间:2014-05-07 20:25:01

标签: c# .net delegates closures

让我提前道歉 - 我可能正在屠杀术语。我对封闭是什么有一个模糊的理解,但无法解释我所看到的行为。至少,我认为这是一个封闭问题。我在网上搜索过,但找不到合适的关键字来获取我想要的内容。

具体来说 - 我有两块完全相似的代码(至少在我看来)。第一:

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

我可以发生了什么 - 但我真的不明白为什么他们会被区别对待。在我的脑海里 - 我喜欢我所看到的原始行为,但后面的例子让我困惑。我希望这是有道理的。感谢

4 个答案:

答案 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(); 
}