技术开发 频道

C#与.NET闭包对比示例解析

  从上面的代码我们不难看到,变量n实际上是属于函数T1的局部变量,它本来生命周期应该是伴随着函数T1的调用结束而被释放掉的,但这里我们却在返回的委托b中仍然能调用它,这里正是闭包所展示出来的威力,因为T1调用返回的匿名委托的代码片段中我们用到了n,而在编译器看来,这些都是合法的,因为返回的委托b和函数T1存在上下文关系,也就是说匿名委托b是允许使用它所在的函数或者类里面的局部变量的,于是编译器通过一系列动作(具体动作我们后面再说)使b中调用的函数T1的局部变量自动闭合,从而使该局部变量满足新的作用范围。

  因此如果你看到.net中的闭包,你就可以像js中那样理解它,由于返回的匿名函数对象是在函数T1中生成的,因此相当于它是属于T1的一个属性。如果你把T1的对象级别往上提升一个层次就很好理解了,这里就相当于T1是一个类,而返回的匿名对象则是T1的一个属性,对属性而言,它可以调用它所寄存的对象T1的任何其他属性或者方法,包括T1寄存的对象TCloser内部的其他属性。如果这个匿名函数会被返回给其他对象调用,那么编译器会自动将匿名函数所用到的方法T1中的局部变量的生命周转期自动提升并与匿名函数的生命周期相同,这样就称之为闭合。

  也许你会说,这个返回的委托包含的变量n只是编译器通过某种方式隐藏的对这个委托对象的一个同样对象的赋值吧,那么我们再对比下面两个方法:

public class TCloser{
public Func<int> T1()
{
var n
= 999;
Func
<int> result = () =>
{
return n;
};
n
= 10;
return result;
}
public dynamic T2()
{
var n
= 999;
dynamic result
=new { A = n };
n
= 10;
return result;
}
static void Main(){
var a
= new TCloser();
var b
= a.T1();
var c
= a.T2();
Console.WriteLine(b());
Console.WriteLine(c.A);
}
}

  最后输出结果是什么呢?答案是10和999,因为闭包的特性,这里匿名函数中所使用的变量就是实际T1中的变量,与之相反的是,匿名对象result里面的A只是初始化时被赋予了变量n的值,它并不是n,所以后面n改变之后A并未随之而改变。这正是闭包的魔力所在。

  你可能会好奇.net本身并不支持函数对象,那么这样的特性又是从何而来呢?答案是编译器,我们一看IL代码便会明白了。

  首先我给出c#代码:

public class TCloser {
public Func<int> T1(){
var n
= 10;
return () =>
{
return n;
};
}
public Func<int> T4(){
return () =>
{
var n
= 10;
return n;
};
}
}
2
相关文章