首页 > 编程语言 >理解C#中的闭包

理解C#中的闭包

时间:2023-06-15 10:11:06浏览次数:38  
标签:闭包 10 Console val C# GetClosureFunction 理解 internalAdd

1、 闭包的含义#

首先闭包并不是针对某一特定语言的概念,而是一个通用的概念。除了在各个支持函数式编程的语言中,我们会接触到它。一些不支持函数式编程的语言中也能支持闭包(如java8之前的匿名内部类)。

在看过的对于闭包的定义中,个人觉得比较清晰的是在《JavaScript高级程序设计》这本书中看到的。具体定义如下:

闭包是指有权访问另一个函数作用域中的变量的函数

注意,闭包这个词本身指的是一种函数。而创建这种特殊函数的一种常见方式是在一个函数中创建另一个函数。

2、 在C# 中使用闭包(例子选取自《C#函数式程序设计》)#

下面我们通过一个简单的例子来理解C#闭包

class Program

{

    static void Main(string[] args)

    {

        Console.WriteLine(GetClosureFunction()(30));

    }



    static Func<int, int> GetClosureFunction()

    {

        int val = 10;

        Func<int, int> internalAdd = x => x + val;



        Console.WriteLine(internalAdd(10));



        val = 30;

        Console.WriteLine(internalAdd(10));



        return internalAdd;

    }

}

上述代码的执行流程是Main函数调用GetClosureFunction函数,GetClosureFunction返回了委托internalAdd并被立即执行了。

输出结果依次为20、40、60

对应到一开始提出的闭包的概念。这个委托internalAdd就是一个闭包,引用了外部函数GetClosureFunction作用域中的变量val。

注意:internalAdd有没有被当做返回值和闭包的定义无关。就算它没有被返回到外部,它依旧是个闭包。

3、 理解闭包的实现原理#

我们来分析一下这段代码的执行过程。在一开始,函数GetClosureFunction内定义了一个局部变量val和一个利用lamdba语法糖创建的委托internalAdd。

第一次执行委托internalAdd 10 + 10 输出20

接着改变了被internalAdd引用的局部变量值val,再次以相同的参数执行委托,输出40。显然局部变量的改变影响到了委托的执行结果。

GetClosureFunction将internalAdd返回至外部,以30作为参数,去执行得到的结果是60,和val局部变量最后的值30是一致的。

val 作为一个局部变量。它的生命周期本应该在GetClosureFunction执行完毕后就结束了。为什么还会对之后的结果产生影响呢?

我们可以通过反编译来看下编译器为我们做的事情。

为了增加可读性,下面的代码对编译器生成的名字进行修改,并对代码进行了适当的整理。

class Program

{

    sealed class DisplayClass

    {

        public int val;



        public int AnonymousFunction(int x)

        {

            return x + this.val;

        }

    }



    static void Main(string[] args)

    {

        Console.WriteLine(GetClosureFunction()(30));

    }



    static Func<int, int> GetClosureFunction()

    {

        DisplayClass displayClass = new DisplayClass();

        displayClass.val = 10;

        Func<int, int> internalAdd = displayClass.AnonymousFunction;



        Console.WriteLine(internalAdd(10));



        displayClass.val = 30;

        Console.WriteLine(internalAdd(10));



        return internalAdd;

    }

}

编译器创建了一个匿名类(如果不需要创建闭包,匿名函数只会是与GetClosureFunction生存在同一个类中,并且委托实例会被缓存,参见clr via C# 第四版362页),并在GetClosureFunction中创建了它实例。局部变量实际上是作为匿名类中的字段存在的。

4、 C#7对于不作为返回值的闭包的优化#

如果在vs2017中编写第二节的代码。会得到一个提示,询问是否把lambda表达式(匿名函数)托转为本地函数。本地函数是c#7提供的一个新语法。那么使用本地函数实现闭包又会有什么区别呢?

如果还是第二节那样的代码,改成本地函数,查看IL代码。实际上不会发生任何变化。

class Program

{

    static void Main(string[] args)

    {

        Console.WriteLine(GetClosureFunction()(30));

    }



    static Func<int, int> GetClosureFunction()

    {

        int val = 10;

        int InternalAdd(int x) => x + val;



        Console.WriteLine(InternalAdd(10));



        val = 30;

        Console.WriteLine(InternalAdd(10));



        return InternalAdd;

    }

}

但是当internalAdd不需要被返回时,结果就不一样了。

下面分别来看下匿名函数和本地函数创建不作为返回值的闭包的时候演示代码及经整理的反编译代码。

匿名函数

static void GetClosureFunction()

{

    int val = 10;

    Func<int, int> internalAdd = x => x + val;



    Console.WriteLine(internalAdd(10));



    val = 30;

    Console.WriteLine(internalAdd(10));

}

经整理的反编译代码

sealed class DisplayClass

{

    public int val;



    public int AnonymousFunction(int x)

    {

        return x + this.val;

    }

}



static void GetClosureFunction()

{

    DisplayClass displayClass = new DisplayClass();

    displayClass.val = 10;

    Func<int, int> internalAdd = displayClass.AnonymousFunction;



    Console.WriteLine(internalAdd(10));



    displayClass.val = 30;

    Console.WriteLine(internalAdd(10));

}

本地函数

class Program

{

    static void Main(string[] args)

    {

    }



    static void GetClosureFunction()

    {

        int val = 10;

        int InternalAdd(int x) => x + val;



        Console.WriteLine(InternalAdd(10));



        val = 30;

        Console.WriteLine(InternalAdd(10));

    }

}

经整理的反编译代码

// 变化点1:由原来的class改为了struct

struct DisplayClass

{

    public int val;



    public int AnonymousFunction(int x)

    {

        return x + this.val;

    }

}



static void GetClosureFunction()

{

    DisplayClass displayClass = new DisplayClass();

    displayClass.val = 10;



    // 变化点2:不再构建委托实例,直接调用值类型的实例方法

    Console.WriteLine(displayClass.AnonymousFunction(10));



    displayClass.val = 30;

    Console.WriteLine(displayClass.AnonymousFunction(10));

}

上述这两点变化在一定程度上能够带来性能的提升,目前的理解是,用结构体代替类,结构体实例能够在方法跑完后就立即释放,不需要等待垃圾回收,所以在官方的推荐中,如果委托的使用不是必要的,更推荐使用本地函数而非匿名函数。

如果本博客描述的内容存在问题,希望大家能够提出宝贵的意见。坚持写博客,从这一篇开始。

 

 

出处:https://www.cnblogs.com/eventhorizon/p/9535289.html

标签:闭包,10,Console,val,C#,GetClosureFunction,理解,internalAdd
From: https://www.cnblogs.com/mq0036/p/17482113.html

相关文章

  • SpringBoot+druid+dynamic+clickhouse+mysql实现读写分离
    背景:clickhouse+mysql实现读写分离1.配置mysql2.安装clickhouse3.使用可视化工具连接clickhouse4.创建clickhouse的数据库并连接mysql5.SpringBoot+druid+dynamic配置多数据源实现读写分离一.背景由于系统数据量过大,查询条件自定义过多,mysql在查询时响应太慢,所以使用cli......
  • windows php7、PHPStudy(小皮)配置oracle扩展
    一、php配置oci扩展1.访问链接下载对应oracle版本的即时客户端(OracleInstantClient)https://www.oracle.com/database/technologies/instant-client/winx64-64-downloads.html  2.解压下载文件,将下面两个文件放入php目录下   3.访问链接https://windows.php.net/down......
  • c#用表达式树实现深拷贝功能
    因为对表达式树有点兴趣,出于练手的目的,试着写了一个深拷贝的工具库。支持.netstandard2.0或.netframework4.5及以上。GitHub地址https://github.com/blurhkh/DeepCopiernuget地址https://www.nuget.org/packages/DeepCopier使用方法如下:首先创建几个测试用的类型pub......
  • DAC+定时器 生成正弦波,三角波
    正弦波生成配置如下DAC触发trigger是由TIM2事件触发。 TIM2配置方式/***生成正弦波数据点函数*@paramNPoints�???个周期内的点�???*@paramVMaxRange输出的电压最大�?�,取�?�范�???0~3.3V*@paramSineWaveTable存放生成的数据点*/voidSineWaveGen......
  • Excel Undo-Redo的编程问题
    ExcelUndo历史栈对外是不透明的。代码对Excel表单的编辑操作会清空Excel内部的Undo历史。Application.OnUndo只支持一次撤销,并且不支持ReDo。使用DDE的方式支持Undo/Redo是现在发现的最佳方案。DDE的问题:异步的,因此和用户在界面上的操作容易产生混乱。需要同步化,但是编程比......
  • Pixelmator Pro for Mac(媲美PS的修图软件)
    PixelmatorProMac版是一款功能强大的PhotoshopMac版替代品,它能轻松地为照片和视频添加任何更改。它提供了一套革命性的工具,可以帮助您在任何照片,视频或幻灯片上进行更改。所有这些工具都非常快速有效。使用PixelmatorPro,您可以轻松地将照片和视频从普通的Photoshop中提取出......
  • Oracle-日期时间
    sysdate1.获取当前时间:selectsydatefromdual;--2023-06-159:38:16selecttrunc(sysdate+1)fromdual;--明天2023-06-16通过trunc截断时间,只保留日期部分selecttrunc(sysdate-1)fromdual;--昨天selecttrunc(sysdate)+12/24fromdual;--2023-06-1512:00:00,......
  • Three.js教程:相机控件轨道控制器OrbitControls
    推荐:将NSDT场景编辑器加入你的3D工具链其他系列工具:NSDT简石数字孪生相机控件轨道控制器OrbitControls平时开发调试代码,或者展示模型的时候,可以通过相机控件OrbitControls实现旋转缩放预览效果。OrbitControls使用你可以打开课件案例源码测试下效果。旋转:拖动鼠标左键缩放......
  • c++ mutex
    mutex直译为互斥量。使用它可以做到:一份共享数据,同时只能由一个线程访问,在此基础上会衍生很多很方便的api,通过mutex将共享数据的访问变成互斥操作,避免与时间相关的错误简单结论及定义:1.每个线程在对资源操作前都尝试先加锁,加锁成功才能操作,操作结束就解锁 2. ......
  • Nginx [emerg] listen() to 0.0.0.0:80, backlog 511 failed (98: Address already in
    Nginx更新后经常遇到这样的问题,解决方法:123fuser-k80/tcp&&/etc/init.d/nginxstart或killall-9nginx&&/etc/init.d/nginxstart......