?!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
如果一个程序设计语a能够用高阶函数解决问题,则意味着数据作用域问题已十分H出。当函数可以当成参数和返回值在函数之间q行传递时Q编译器利用闭包扩展变量的作用域Q以保证随时能得到所需要的数据?/span>
C#函数式程序设计之作用?/span>
在C#中,变量的作用域是严格确定的。其本质是所有代码生存在cȝҎ中、所有变量只生存于声明它们的模块中或者之后的代码中。变量的值是可变的,一个变量越是公开Q带来的问题p严重。一般的原则是,变量的值最好保持不变,或者在最的作用域内保存其倹{一个纯函数最好只使用在自q模块中定义的变量|不访问其作用域之外的M变量?/span>
遗憾的是Q有时我们无法把变量的值限制于函数的范围内。如果在E序的初始化时定义了几个变量Q在后面需要反复用到它们,怎么办?一个可能的办法是用闭包?/span>
C#函数式程序设计之闭包机制
Z理解闭包的本质,我们分析几个使用闭包的例子:
namespace Closures
{
class Closures
{
static void Closures()
{
Console.WriteLine(GetClosureFunc()(30));
}
static Func<int,int> GetClosureFunc()
{
int val = 10;
Func<int, int> internalAdd = x => x + val;
Console.WriteLine(internalAdd(10));
val = 30;
Console.WriteLine(internalAdd(10));
return internalAdd;
}
}
}
此代码的l果输出是多?{案?0 40 60Q前面两个|大家应该很容易就能看出来Q但W三个gؓ什么是60呢?先来看看E序的执行流E:Closures函数调用GetClosureFunc函数q进入其中。函数调用语句中带了一个参?0。这是由于GetClosureFuncq回的是一个函敎ͼx行时再次调用了这个函敎ͼq入GetClosureFunc函数中,首先val的gؓ10Q通过internalAddҎ传入一个?0Q因此第一个输出gؓ20Q往下走Qval的值变?0Q通过internalAddҎ传入?0Q于是第二个输出gؓ40。从q里我们大致可以看出Q局部函数和局部变量如何在同一个作用域中v作用Q显Ӟ对局部变量的改变会媄响internalAdd的|管变量的改变发生在internalAdd最初的创徏之后。最后,GetClosureFuncq回了internalAddҎQ以参数30再次调用q个函数Q于是,l果成ؓ60?/span>
初看hQ这q不真正W合逻辑。val应该是一个局部变量,它生存在栈中Q当GetClosureFunc函数q回Ӟ它就不在了,不是么?实如此Q这正是闭包的目的,当编译器会明白无误地警告q种情况会引L序的崩溃旉止变量D出其作用域之外?/span>
从技术角度来看,数据保存的位|很重要Q编译器创徏一个匿名类Qƈ在GetClosureFunc中创个类的实例——如果不需要闭包v作用Q则那个匿名函数只会与GetClosureFunc生存在同一个类中,最后,局部变量val实际上不再是一个局部变量,而是匿名cM的一个字Dc其l果是,internalAdd现在可以引用保存在匿名类实例中的函数。这个实例中也包含变量val的数据。只要保持internalAdd的引用,变量val的值就一直保存着?/span>
下面q段代码说明~译器在q种情Ş下采用的模式Q?/span>
private sealed class DisplayClass
{
public int val;
public int AnonymousFunc(int x)
{
return x + this.val;
}
private static Func<int, int> GetClosureFunc()
{
DisplayClass displayClass = new DisplayClass();
displayClass.val = 10;
Func<int, int> internalAdd = displayClass.AnonymousFunc;
Console.WriteLine(internalAdd(10));
displayClass.val = 30;
Console.WriteLine(internalAdd(10));
return internalAdd;
}
}
回到动态创建函数思想Q现在可以凭I创建新的函敎ͼ而且它的功能因参数而异。例如,下面q个函数把一个静态值加C个参CQ?/span>
private static void DynamicAdd()
{
var add5 = GetAddX(5);
var add10 = GetAddX(10);
Console.WriteLine(add5(10));
Console.WriteLine(add10(10));
}
private static Func<int,int> GetAddX(int staticVal)
{
return x => staticVal + x;
}
q个原理正是许多函数构徏技术的基础Q这U方法显然与Ҏ重蝲{面向对象方法相对应。但是与Ҏ重蝲不同Q匿名函数的创徏可以在运行时动态发生,只需受另一个函C的一行代码触发。ؓ使某个算法更加容易读和写而用的Ҏ函数可以在调用它的方法中创徏Q而不是再cȝ别上胡ؕd函数或方法——这正是函数模块化的核心思想?/span>