C# 闭包

什么是闭包

简单来讲,闭包允许你将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。这样可以使控制结构、逻辑操作等从调用细节中分离出来。访问原来上下文的能力是闭包区别一般对象的重要特征,尽管在实现上只是多了一些编译器技巧。

[C#和Java的闭包:http://www.zxbc.cn/html/20080516/34373.html]

闭包的副作用

闭包并不是新概念,在LINQ的使用中已经证明了它难以置信地实用。但是,在它使用时如果破坏了封装,确实会带来明显的副作用。当把两个似乎无关的功能放在一起使用,就会出现意料不到的结果。
闭包允许函数把它们的本地变量共享给定义在这些函数内部的匿名函数。这些匿名函数通常被称作lambda表达式,对于创建由LINQ暴露的强类型查询语句来说是必不可缺的。
Dustin Campbell在对LINQ的实验中,发现查询语句在执行的时候能够被改变。的确如此,通过修改用在闭包代码中的本地变量,用于while子句的功能也就被修改了。如果这是在查询语句执行的时候改变的话,那么相应的查询语句也会有对应的变化和结果。
Dustin使用这个技巧去创建一个仅仅返回不重复项目的查询语句。最初,where子句是"m.Name != filter"。每次当一个条目被返回,filter的值随着条目的值改变。在这种情况下,查询语句能够成功地创建一个不重复的列表。
然而,这个技巧也是及其脆弱的。在Dustin的例子中,列表必须在where子句调用前先排好序。如果不这样做,过滤操作会在所有条目被返回前触发,这样也就没有机会去改变查询语句的行为了。因为where和order by子句可以以任意次序先后出现,这个功能在LINQ中是支持的。
Dustin没有提到的是,这个情况不会出现在所有的LINQ Provider中。那些要把查询语句交给像LINQ to SQL这样外部语法引擎的Provider,就没有机会改变where子句了。如果采用并行LINQ(Parallel LINQ),情况会更糟,因为在多个线程运行的时候,任何对where子句的改变都会引发竞态条件(race condition)。

当然,正确的方法应该是只调用Disctinct()就可以了。尽管这些技巧在理论上很有意思,但它们肯定会引发一些微妙的bug,而且也很容易收到框架中变化的影响。

 

昨天跟几个朋友闲谈时,提到C++的函数指针与C#的方法对象,其中讨论了一个在学习C#中很少提到的概念"闭包"
闭包
闭包是将一些执行封装,将它像对象一样传递,在传递时,执行依然能够访问到原上下文。
访问原来上下文,是闭包的重要特征
例:

 

static void Main(string[] args)
{
    List<Action> ls = new List<Action>();
    for (int i = 0; i < 10; i++)
    {
        ls.Add(() => Console.WriteLine(i));
    }
    foreach (Action action in ls)
    {
        action();
    }
    System.Console.Read();
}

由于只声明了一个i变量
所以所有的Action捕获的都是同一个i变量。结果就是每一行都输出数字10

 

以下方式实现了输出0到9
与上例代码的唯一不是是在循环体中使用了一个局部变量tp,这种写法在通常看来不通是多用了一个中转变量,对程的执行不会有什么影响,但事实上tp这个变量在被每个Action独立保存.
这样,每次循环体在执行的时候,都会取得一个全新的tp,而且tp不会因为所在声名体的完成而出栈

 

static void Main(string[] args)
{
    List<Action> ls = new List<Action>();
    for (int i = 0; i < 10; i++)
    {
        int tp = i;
        ls.Add(() => Console.WriteLine(tp));
    }
    foreach (Action action in ls)
    {
        action();
    }
    System.Console.Read();
}

 

若匿名方法中的变量:
若匿名方法中如果引用了某个变量,则该局部变量将被提升为实例变量,并储存于一个叫做闭包(closure)的对象中。
提升之后,即使创建该变量的方法执行完毕该变量仍不会消亡。
当指向该匿名函数的所有引用都消失后,该闭包变量即可正常地被垃圾回收器回收
说明

class Program
{
    delegate int wxd(int i);
    delegate wxd lzm(int ii);
    static void Main(string[] args)
    {
        lzm obj = delegate(int ii)
        {
            return
                delegate(int i)
                {
                    return i + ii;
                };
        };
        wxd w1 = obj(1);
        wxd w2 = obj(2);
        System.Console.WriteLine(w1(3));
        System.Console.WriteLine(w2(3));
        System.Console.Read();
    }
}

 

输出的结果是4和5
[obj]函数接受一个参数,返回新的函数[w1,w2]。新的函数[w1,w2]将[obj]的参数与自己的参数相加,返回结果
函数[w1,w2]在接受相同的参数的时候,产生了不同的结果。
实际上[obj]返回的内部函数已经把[obj]的参数[ii]记录了
在这里,方法已不仅仅是一个函数指针了
[obj]的参数[ii]是如何记录的:
通常理解,函数的参数是放在栈中的。
如果闭包也将参数放在栈中,那么[ii]在[obj]运行结束的时候就会消失掉,这个时候[w1,w2]再通过栈去搜索[ii]显然就是不可能的。
所以闭包中参数或内部变量不能放在栈中.而是放在程序执行过程之中的一张全局表里.
[[obj]在返回内部函数的时候,将全局表,自己的结构表,内部函数的指针一起传递给变量[w1,w2].
这时内部函数可以访问[ii],外部却无法访问[ii]

posted @ 2009-11-21 23:37  pursue  阅读(818)  评论(0编辑  收藏  举报