Closure的思考

什么是Closure?

在我的这篇博客中谈到了代理的行为不是我期望的结果的疑问。经过学习理解发现是有关Closure的知识,Closure是在函数式编程比如F#,Haskell等中的一个重要的概念。Clousre是指外部变量被绑定在函数内部。这样理解起来可能比较抽象,用C#示例代码说起来可能比较容易理解,在C#中涉及到Closure的主要是匿名代理和lambda表达式等。首先看一个示例:
using System;

namespace Test
{
   
class Program
    {
       
static Action GetPrinter()
        {
           
int i = 0;
            Action action
= () => Console.WriteLine(i);
           
return action;
        }

       
static void Main(string[] args)
        {
            Action action
= GetPrinter();
            action();
        }
    }
}
上面的例子可以看出,在Main函数中可以通过action这个代理来访问到GetPrinter函数中的变量i的值,不但如此,任何对变量i值的改变都能影响到action这个代理。这在普通函数中是不可能达到的,因为函数之间是不能互相访问内部的。因此,在这篇博客中提到的问题也就容易理解了,问题就出在这段代码中:
//Loops all the objects and creates operations.
foreach (var myObject in myObjects)
{
   
//Instantiates an operation that printing my object's name.
    Action action = () => { Console.WriteLine(myObject.Name); };
    operators.Add(
new Operation(action));
}
对于每一次循环而言,可以正确的生成一个Action,这个Action引用到了当前的循环变量myObject,然后当循环变量被替换到下个myObject时,已生成的Action也都指向了当前的myObject。以此类推,到最后所有的Action引用的都是最后一个myObject,也就造成了文中所述的结果。或许你认为这是Closure的一个缺点,或者是一个特有的功能。不管如何,Closure的行为就是这样,了解了它的行为我们便可在今后的开发中避免一些不必要的错误。
那么有个疑问是,编译器是如何构造Closure的呢?
对于Closure,C#编译器会为其构造出一个类来,类中的某个函数即为用户定义的代理(比如本文中的Action action),而此代理所引用的的变量(专指来自于所定义代理的环境中的变量,比如本文中的 int i)则变成编译器所构造出来的类的某一个成员。也就是通过这样的行为,编译器成功的实现了Closure。对于本文中的例子,编译器为其自动生成的的类可以这样描述:
class ActionClosure
{
   
public int i = 0;
   
public void action()
    {
        Console.WriteLine(i);
    }
}
当然了,类的名字不是上面所描述的,而是编译器给的一个特殊的名字以防止和用户定义的类命名冲突。此处用这个名字只是为了看起来更清晰。 任何对原有i值的改变都会影响到ActionClosure.i的值。

例如下面的代码:

            int i = 0;
            Action action
= () => Console.WriteLine(i); 
            i = 1;
会导致ActionClosure发生如下代码所示的行为:
            ActionClosure closure = …
            closure.i = 0;
            closure.i = 1;            

本文参考了这篇很不错的文章:http://diditwith.net/2007/02/09/WhatsInAClosure.aspx,本文所引用的内容版权归原作者所有。

原文地址:https://www.cnblogs.com/lsp/p/1643565.html