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

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

总结

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

标签:闭包封装
0
投稿

猜你喜欢

  • SpringBoot使用FreeMarker模板发送邮件

    2021-07-27 07:32:26
  • java实现二分法的完整代码

    2023-08-18 22:09:06
  • Spring @Profile注解详解

    2023-04-20 06:26:16
  • Springboot-注解-操作日志的实现方式

    2023-10-10 13:54:35
  • Java 代理(Proxy)的原理及应用

    2023-08-21 08:40:41
  • SpringBoot 集成 activiti的示例代码

    2023-01-22 10:22:03
  • mybatis根据表逆向自动化生成代码的实现

    2023-03-12 15:39:02
  • springboot中的静态资源加载顺序优先级

    2023-08-24 11:12:31
  • Java深入了解数据结构之栈与队列的详解

    2022-03-24 08:12:27
  • Seata AT模式TransactionHook被删除探究

    2022-01-12 14:56:49
  • android实现蓝牙app代码

    2021-07-08 07:52:15
  • jdk源码阅读Collection详解

    2022-07-25 08:57:40
  • 图文浅析Java序列化和反序列化

    2022-08-03 17:46:59
  • SpringBoot整合Swagger的方法示例

    2021-05-29 02:42:16
  • Android IdleHandler使用方法详解

    2023-11-17 20:48:11
  • 解决java.util.NoSuchElementException异常的问题

    2023-02-10 08:26:46
  • Android自定义圆形倒计时进度条

    2021-09-11 06:17:08
  • 完美解决Spring声明式事务不回滚的问题

    2023-07-12 14:38:50
  • AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

    2023-02-24 22:20:09
  • SpringBoot访问静态资源的配置及顺序说明

    2022-07-18 13:53:31
  • asp之家 软件编程 m.aspxhome.com