如果一个程序设计语言能够用高阶函数解决问题,则意味着数据作用域问题已十分突出。当函数可以当成参数和返回值在函数之间进行传递时,编译器利用闭包扩展变量的作用域,以保证随时能得到所需要的数据。

C#函数式程序设计之作用域

在C#中,变量的作用域是严格确定的。其本质是所有代码生存在类的方法中、所有变量只生存于声明它们的模块中或者之后的代码中。变量的值是可变的,一个变量越是公开,带来的问题就越严重。一般的原则是,变量的值最好保持不变,或者在最小的作用域内保存其值。一个纯函数最好只使用在自己的模块中定义的变量值,不访问其作用域之外的任何变量。

遗憾的是,有时我们无法把变量的值限制于函数的范围内。如果在程序的初始化时定义了几个变量,在后面需要反复用到它们,怎么办?一个可能的办法是使用闭包。

C#函数式程序设计之闭包机制

为了理解闭包的本质,我们分析几个使用闭包的例子:

namespace Closures
{
class ClosuresClass
{
static void ClosuresTest()
{
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;
}
}
}

此代码的结果输出是多少?答案是20  40  60,前面两个值,大家应该很容易就能看出来,但第三个值为什么是60呢?先来看看程序的执行流程:Closures函数调用GetClosureFunc函数并进入其中。函数调用语句中带了一个参数30。这是由于GetClosureFunc返回的是一个函数,即执行时再次调用了这个函数,进入GetClosureFunc函数中,首先val的值为10,通过internalAdd方法传入一个值10,因此第一个输出值为20,往下走,val的值变成30,通过internalAdd方法传入值10,于是第二个输出值为40。从这里我们大致可以看出,局部函数和局部变量如何在同一个作用域中起作用,显然,对局部变量的改变会影响internalAdd的值,尽管变量的改变发生在internalAdd最初的创建之后。最后,GetClosureFunc返回了internalAdd方法,以参数30再次调用这个函数,于是,结果成为60。

初看起来,这并不真正符合逻辑。val应该是一个局部变量,它生存在栈中,当GetClosureFunc函数返回时,它就不在了,不是么?确实如此,这正是闭包的目的,当编译器会明白无误地警告这种情况会引起程序的崩溃时阻止变量值超出其作用域之外。

从技术角度来看,数据保存的位置很重要,编译器创建一个匿名类,并在GetClosureFunc中创建这个类的实例——如果不需要闭包起作用,则那个匿名函数只会与GetClosureFunc生存在同一个类中,最后,局部变量val实际上不再是一个局部变量,而是匿名类中的一个字段。其结果是,internalAdd现在可以引用保存在匿名类实例中的函数。这个实例中也包含变量val的数据。只要保持internalAdd的引用,变量val的值就一直保存着。

下面这段代码说明编译器在这种情形下采用的模式:

    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;
}
}

回到动态创建函数思想:现在可以凭空创建新的函数,而且它的功能因参数而异。例如,下面这个函数把一个静态值加到一个参数上:

        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;
}

这个原理正是许多函数构建技术的基础,这种方法显然与方法重载等面向对象方法相对应。但是与方法重载不同,匿名函数的创建可以在运行时动态发生,只需受另一个函数中的一行代码触发。为使某个算法更加容易读和写而使用的特殊函数可以在调用它的方法中创建,而不是再类级别上胡乱添加函数或方法——这正是函数模块化的核心思想。

总结

闭包是程序设计语言支持函数式设计方法的一个重要工具。

C#函数式程序设计之用闭包封装数据的更多相关文章

  1. C#函数式程序设计之代码即数据

    自3.5版本以来,.NET以及微软的.NET语言开始支持表达式树.它们为这些语言的某个特定子集提供了eval形式的求值功能.考虑下面这个简单的Lambda表达式: Func<int, int, ...

  2. C#函数式程序设计之函数、委托和Lambda表达式

    C#函数式程序设计之函数.委托和Lambda表达式 C#函数式程序设计之函数.委托和Lambda表达式   相信很多人都听说过函数式编程,提到函数式程序设计,脑海里涌现出来更多的是Lisp.Haske ...

  3. C#函数式程序设计之泛型(下)

    C#函数式程序设计之泛型(下)   每当使用泛型类型时,可以通过where字句对泛型添加约束: + 这个例子直观地声明了一个约束:类型T必须与ListItem<string>相匹配.泛型类 ...

  4. C#函数式程序设计之泛型

    Intellij修改archetype Plugin配置 2014-03-16 09:26 by 破狼, 204 阅读, 0 评论,收藏, 编辑 Maven archetype plugin为我们提供 ...

  5. C#函数式程序设计之惰性列表工具——迭代器

    有效地处理数据时当今程序设计语言和框架的一个任务..NET拥有一个精心构建的集合类系统,它利用迭代器的功能实现对数据的顺序访问. 惰性枚举是一个迭代方法,其核心思想是只在需要的时候才去读取数据.这个思 ...

  6. C#函数式程序设计之泛型(上)

    在面向对象语言中,我们可以编写一个元素为某个专用类型(可能需要为此创建一个ListElement)的List类,或者使用一个非常通用.允许添加任何类型元素的基类(在.NET中,首先想到的是System ...

  7. C#函数式程序设计之局部套用与部分应用

    函数式设计的核心与函数的应用以及函数如何作为算法的基本模块有关.利用局部套用技术可以把所有函数看成是函数类的成员,这些函数只有一个形参,有了局部套用,才有部分应用.部分应用是使函数模块化成为可能的两个 ...

  8. JSP-07-使用JavaBean封装数据

    7.1 常命包名 Dao 包中的接口(NewsDao)以及类(NewsDaoImpl)注意负责和数据操作相关的事情. Service 包中的接口和类对dao的方法进行封装和调用,注意负责和业务逻辑相关 ...

  9. json和xml封装数据、数据缓存到文件中

    一.APP的通信格式之xml xml:扩展标记语言,可以用来标记数据,定义数据类型,是一种允许用户对自己标记语言进行定义的源语言.XML格式统一,扩平台语言,非常适合数据传输和通信,业界公认的标准. ...

随机推荐

  1. SVN(TortoiseSVN)提交时忽略bin跟obj目录

    SVN(TortoiseSVN)提交时忽略bin和obj目录 一般协作开发情况下,有意思无意将bin和obj目录添加到版本管理中是很烦人的事儿,在VS中不断地编译程序集和提交将带来版本暴增问题.如果你 ...

  2. JS 获取自定义标签

    <abc-aaa xwe='sdf'>AAAAAAAAAAAAAAAAAAAAAA</abc-aaa> alert($("abc-aaa").attr(&q ...

  3. 巧用css border

    上下左右边框交界处呈现平滑的斜线.利用这个特点,通过设置不同的上下左右边框宽度或颜色,可以得到小三角.梯形等. 调整宽度大小可以调节三角形形状. 实现三角形 示例1: #test1{ height:2 ...

  4. 【LeetCode】273. Integer to English Words

    Integer to English Words Convert a non-negative integer to its english words representation. Given i ...

  5. [转]说说C#的async和await

    C# 5.0中引入了async 和 await.这两个关键字可以让你更方便的写出异步代码. 看个例子: public class MyClass { public MyClass() { Displa ...

  6. IP和端口的意义

    百科说明 在网络技术中,端口(Port)包括逻辑端口和物理端口两种类型.物理端口指的是物理存在的端口,如ADSL Modem.集线器.交换机.路由器上用 于连接其他网络设备的接口,如RJ-45端口.S ...

  7. emoji和utf8mb4字符集

    mysql 的 utf8 不支持 emoji,需要修改设置为utf8mb4 <?php 'mysql' => [ 'charset' => 'utf8mb4', 'collation ...

  8. [Javascript] Decorators in JavaScript

    First, what is 'High Order function', basic just a function, inside the function return another fuct ...

  9. 一致性哈希算法 - consistent hashing

    1 基本场景比如你有 N 个 cache 服务器(后面简称 cache ),那么如何将一个对象 object 映射到 N 个 cache 上呢,你很可能会采用类似下面的通用方法计算 object 的 ...

  10. visual studio 2013使用技巧

    去掉 引用提示 文本编辑器=>所有语言=>codelens visual studio 由于以前的函数求值超时,函数求值被禁用.必须继续执行才能重新启用函数求值 visual studio ...