C#函数式程序设计之用闭包封装数据的实现代码
发布时间:2021-06-30 01:44:34
C#函数式程序设计之作用域
在C#中,变量的作用域是严格确定的。其本质是所有代码生存在类的方法中、所有变量只生存于声明它们的模块中或者之后的代码中。变量的值是可变的,一个变量越是公开,带来的问题就越严重。一般的原则是,变量的值最好保持不变,或者在最小的作用域内保存其值。一个纯函数最好只使用在自己的模块中定义的变量值,不访问其作用域之外的任何变量。
遗憾的是,有时我们无法把变量的值限制于函数的范围内。如果在程序的初始化时定义了几个变量,在后面需要反复用到它们,怎么办?一个可能的办法是使用闭包。
C#函数式程序设计之闭包机制
为了理解闭包的本质,我们分析几个使用闭包的例子:
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;
}
}
}
此代码的结果输出是多少?答案是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;
}
这个原理正是许多函数构建技术的基础,这种方法显然与方法重载等面向对象方法相对应。但是与方法重载不同,匿名函数的创建可以在运行时动态发生,只需受另一个函数中的一行代码触发。为使某个算法更加容易读和写而使用的特殊函数可以在调用它的方法中创建,而不是再类级别上胡乱添加函数或方法——这正是函数模块化的核心思想。
总结
闭包是程序设计语言支持函数式设计方法的一个重要工具。


猜你喜欢
- 看到当上面的对话框弹出时,可以使用命令查看顶层的活动窗口adb shell dumpsys window | findstr mCurren
- 在 C语言中,占位符是一种用于格式化输出的特殊字符,通常用于 printf() 等输出函数中,用于指定输出的格式和内容。在本文中,我们将详细
- class 参数 {
- W3C制定了XML DOM标准,.Net为了支持W3C的标准,从1.1版本开始就引入了XmlDocument类。我在前一篇博客中,介绍了如何
- 在本博客中,可以找到一篇《c#实现输出的字符靠右对齐的示例》它有教大家怎样实现字符串输出进行左齐或者是右对齐。本篇的方法,超简单,是使用st
- 前言对于正则表达式,相信很多人都知道,但是很多人的第一感觉就是难学,因为看第一眼时,觉得完全没有规律可寻,而且全是一堆各种各样的特殊符号,完
- 分转元private String fenToYuan(String amount){ NumberFormat format
- 目录1 起因2 解决方案2.1 在pom中引入2.2 Spring Boot 打包处理2.3 拓展: Spring Boot 打包加入其它资
- 本文实例讲述了Spring实战之Bean的作用域singleton和prototype用法。分享给大家供大家参考,具体如下:一 配置<
- 本文实例讲述了Java针对封装数组的简单复杂度分析方法。分享给大家供大家参考,具体如下:完成了数组的封装之后我们还需对其进行复杂度分析:此处
- 开发工具下载:Tomcat下载:wget http://learning.happymmall.com/tomcat/apache-tomc
- 简介Pyjnius是一个用于访问Java类的Python库。适用场景:极个别的加密算法等内容,用python不方便实现或者实现较耗时,可基于
- SpringBoot默认的页面映射路径(即模板文件存放的位置)为“classpath:/templates/*.html”。静态文件路径为“
- 前段时间,我写一个树的访问算法的时候,用了Visitor模式把访问的算法分离了出来,当时打算用lambda表达式写visit算法的,却发现带
- 一、顺序结构程序的执行和代码的执行顺序有关,如果调整代码的书写顺序, 则执行顺序也发生变化二、分支结构基本语法形式1:if(布尔表达式){
- 这篇文章主要介绍了Java GZIP压缩与解压缩代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 应用场景假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费如果仓库中没有产品,则生产者将产品放入仓库,否
- 背景最近公司的客户要求,分配给员工的任务除了有微信通知外,还希望PC端的网页也能实时收到通知。管理员分配任务是在我们的系统A,而员工接受任务
- 在之前的文章中,我们介绍了JDK14中jstat工具的使用,本文我们再深入探讨一下jstack工具的使用。jstack工具主要用来打印jav
- Bat.aspx: 程序代码 <%@ Page Language="C#" AutoEventWireu