闭包函数的对比

“为什么说Java匿名内部类是残缺的闭包”

Java

无意间看到一篇文章谈到java的闭包函数,于是引出了一个stackoverflow的问题。

[#]Stackoverflow : Cannot refer to a non-final variable inside an inner class defined in a different method

接受回答的第一个评论:

Not quite true, Java does generate captures for the variables in question to capture their run-time values, its just they wanted to avoid a strange side-effect that is possible in .Net where by you capture the value in the delegate, change the value in the outer method, and now the delegate sees the new value see, stackoverflow.com/questions/271440/c-captured-variable-in-lo‌op for the C# example of this behaviour that Java aims to avoid.

Java 的闭包函数由于一些原因(?)没有捕捉变量,于是用final方法对变量做手脚,编译成全局常量(猜测)。

Csharp

[#] Stackoverflow : Captured variable in a loop in Csharp

Question:

I expect it to output 0, 2, 4, 6, 8. However, it actually outputs five 10s.

List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    actions.Add(() => variable * 2);
    ++ variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}

Answer:

Yes - take a copy of the variable inside the loop:

while (variable < 5)
{
    int copy = variable;
    actions.Add(() => copy * 2);
    ++ variable;
}

You can think of it as if the C# compiler creates a “new” local variable every time it hits the variable declaration. In fact it’ll create appropriate new closure objects, and it gets complicated (in terms of implementation) if you refer to variables in multiple scopes, but it works :)

Note that a more common occurrence of this problem is using for or foreach:

for (int i=0; i < 10; i++) // Just one variable
foreach (string x in foo) // And again, despite how it reads out loud

See section 7.14.4.2 of the C# 3.0 spec for more details of this, and my article on closures has more examples too.

总结

这里说明了捕捉变量的过程,实际上提问的代码里 lambda函数与variable变量运行时产生的值没有形成闭包。而对循环的修改即实现了这个过程。

{
    List<Func<int>> actions = new List<Func<int>>();
    int variable = 0;
    while (variable < 5)
    {
        actions.Add(() => variable * 2);
        ++ variable;
    }

    foreach (var act in actions)
    {
        Console.WriteLine(act.Invoke());
    }
}
{
    List<Func<int>> actions = new List<Func<int>>();
    int variable = 0;
    while (variable < 5)
    {
        int copy = variable;
        actions.Add(() => copy * 2);
        ++ variable;
    }

    foreach (var act in actions)
    {
        Console.WriteLine(act.Invoke());
    }
}

实际上大部分语言的实现C#是一样的,个人觉得这样也比较符合思维习惯。