C#中闭包概念讲解

理解C#中的闭包

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

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

到此这篇关于C#中闭包概念讲解的文章就介绍到这了,更多相关C# 闭包内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#延迟执行方法函数实例讲解

    需求分析: 我们在做winform开发的时候,有时候需要让程序休眠几秒钟,但是如果我们直接使用 Thread.Sleep()函数的话,页面UI就会停止响应.怎么样解决呢,你可以把页面涉及到表现UI的代码放到一个单线程处理,也可以采用我面的方法,加一个小函数就ok了. if (MessageBox.Show("确定要清理吗?", "确认", MessageBoxButtons.YesNo) == DialogResult.Yes) { this.labMsg.Text

  • 如何在C#9 中使用static匿名函数

    匿名函数 在 C# 中已经出现很多年了,虽然匿名函数用起来很爽,但代价是不小的,为了避免不必要那些你意想不到的内存分配,这就是为什么 C#9 中引入 static 匿名函数的原因,这篇文章我们将会讨论如何使用 静态匿名函数 以及为什么要用. 匿名方法的代价 匿名方法代价不低,因为它有 委托调用 方面的开销,什么意思呢?如果你的 lambda 里需要捕获封闭方法的局部变量或者参数,那么就会存在两种堆分配,一种是委托上的分配,另一种是闭包上的分配,如果你的 lambda 仅仅捕获一个封闭方法的实例状

  • C#函数式程序设计之用闭包封装数据的实现代码

    C#函数式程序设计之作用域 在C#中,变量的作用域是严格确定的.其本质是所有代码生存在类的方法中.所有变量只生存于声明它们的模块中或者之后的代码中.变量的值是可变的,一个变量越是公开,带来的问题就越严重.一般的原则是,变量的值最好保持不变,或者在最小的作用域内保存其值.一个纯函数最好只使用在自己的模块中定义的变量值,不访问其作用域之外的任何变量. 遗憾的是,有时我们无法把变量的值限制于函数的范围内.如果在程序的初始化时定义了几个变量,在后面需要反复用到它们,怎么办?一个可能的办法是使用闭包. C

  • 基于c# Task自己动手写个异步IO函数

    前言 对于服务端,达到高性能.高扩展离不开异步.对于客户端,函数执行时间是1毫秒还是100毫秒差别不大,没必要为这一点点时间煞费苦心.对于异步,好多人还有误解,如: 异步就是多线程:异步就是如何利用好线程池.异步不是这么简单,否则微软没必要在异步上花费这么多心思.本文就介绍异步最新的实现方式:Task,并自己动手写一个异步IO函数.只有了解了异步函数内部实现方式,才能更好的利用它. 对于c#,异步处理经过了多个阶段,但是对于现阶段异步就是Task,微软用Task来抽象异步操作.以后的异步函数,处

  • C#中函数的创建和闭包的理解

    动态创建函数 大多数同学,都或多或少的使用过.回顾下c#中动态创建函数的进化: C# 1.0中: 复制代码 代码如下: public delegate string DynamicFunction(string name);   public static DynamicFunction GetDynamicFunction()   {       return GetName;   }   static string GetName(string name)   {       return

  • C# Split函数根据特定分隔符分割字符串的操作

    在C#程序开发过程中,很多时候可能需要将字符串根据特定的分割字符分割成字符或者List集合,例如根据逗号将字符串分割为数组,或者根据竖线将字符串分割成数组,C#中提供了Split()函数来快速将字符串分割成数组形式,如果需要转换为List集合,可在分割完成后使用数组的ToList()方法即可转换为List集合数据. 例如下列例子,将字符str按照逗号分隔成数组. string str = "A,B,C,D,E,F,G"; string[] strArr = str.Split(',')

  • C#中闭包概念讲解

    理解C#中的闭包 1. 闭包的含义 首先闭包并不是针对某一特定语言的概念,而是一个通用的概念.除了在各个支持函数式编程的语言中,我们会接触到它.一些不支持函数式编程的语言中也能支持闭包(如java8之前的匿名内部类). 在看过的对于闭包的定义中,个人觉得比较清晰的是在<JavaScript高级程序设计>这本书中看到的.具体定义如下: 闭包是指有权访问另一个函数作用域中的变量的函数. 注意,闭包这个词本身指的是一种函数.而创建这种特殊函数的一种常见方式是在一个函数中创建另一个函数. 2. 在C#

  • javascript中闭包概念与用法深入理解

    本文实例分析了javascript中闭包概念与用法.分享给大家供大家参考,具体如下: 1.问题的引出,什么时候会遇到闭包? 首先因为JS是没有块状作用域的,但是有函数作用域即函数作为了局部变量之间的界限,不同函数内的局部变量具有独立性, 因为JS没有块状作用域,笔者初学JS时,在事件的监听时,因为不理解JS中局部变量的作用域,犯过不少错误! (1)JS中的变量作用域 for(var i=0;i<9;i++) { } alert(i) //输出9 我们发现,虽然变量i是块状区域for()内的一个局

  • javascript中的闭包概念与用法实践分析

    本文实例讲述了javascript中的闭包概念与用法.分享给大家供大家参考,具体如下: 闭包的概念:闭包是指有权访问另一个函数作用域中的变量的函数 (引自<javascript高级程序设计第三版>178页).闭包的优点是不会产生全局变量,避免变量污染问题,但是闭包也有一个缺点就是闭包携带包含它的函数作用域会比其它函数占用更多的内存,过度使用会导致内存占用过多. wiki上关于闭包的概念: In programming languages, closures (also lexical clos

  • MySQL中Like概念及用法讲解

    Like中文解释为喜欢的意思,但当应用于MySQL数据库中,Like则是一种语句,用于模糊查询,主要是针对字符型字段的,在一个字符型字段列中检索包含对应子串的.本文向大家介绍MySQL中Like语句. 一.Like是什么意思 1.Like算作MySQL中的谓词,其应用与is.=.>和<等符号用法类似. 2.在sql结构化查询语言中,like语句有着至关重要的作用. 3.从某种意义上讲,Like可看作是一个精简的正则表达式功能. 二.Like作用 like语句的语法格式是 select * fr

  • javascript中闭包closure的深入讲解

    简介 闭包closure是javascript中一个非常强大的功能.所谓闭包就是函数中的函数,内部函数可以访问外部函数的作用域范围,从而可以使用闭包来做一些比较强大的工作. 今天将会给大家详细介绍一下闭包. 函数中的函数 我们提到了函数中的函数可以访问父函数作用域范围的变量,我们看一个例子: function parentFunction() { var address = 'flydean.com'; function alertAddress() { alert(address); } al

  • Java中StringUtils与CollectionUtils和ObjectUtil概念讲解

    目录 一.解析 概念 StringUtils概念 CollectionUtils概念 ObjectUtil概念 二.区别 三.总结 一.解析 概念 StringUtils概念 StringUtils 方法的操作对象是 Java.lang.String 类型的对象,是 JDK 提供的 String 类型操作方法的补充,并且是 null 安全的(即如果输入参数 String 为 null 则不会抛出 NullPointerException ,而是做了相应处理,例如,如果输入为 null 则返回也是

  • Swift 中闭包的简单使用

    本文主要是介绍Swift中闭包的简单使用,将从"闭包的定义"."闭包的创建.赋值.调用"."闭包常见的几种使用场景","使用闭包可能引起的循环强引用" 四个方面入手,重点介绍闭包如何使用,没有高深的概念,只是专注于实际使用,属于入门级水平,后面还会有关于闭包更加详细和深入理解的文章.希望大家在阅读完本文后能够对闭包有一个整体的理解以及能够简单的使用它. 闭包的定义 在Swift开发文档中是这样介绍闭包的:闭包是可以在你的代码中

  • javascript闭包概念简单解析(推荐)

    关于"闭包"这个概念的文章在网上铺天盖地,基本已经稀烂了,但是有时候总感觉读了这么多的文章还是云山雾罩,当然是由于它本身就比较难于理解和涉及的知识较多,还有一个很重要的原因就是网上很多教程介绍可能存在一定的误区,或者说侧重点不同,下面就通过代码实例简单的介绍一下什么是闭包. 代码实例一: function a(){ var webName="我们"; console.log(webName); } a() 以上是一段非常简单的代码,当函数执行结束之后,它就会从内存中

  • Spring中AOP概念与两种动态代理模式原理详解

    目录 1.概念 1.AOP技术简介 2.AOP的优势 3.Spring AOP术语 4.AOP 开发明确的事项 2.AOP底层实现 1.AOP 的动态代理技术: 3.基于cglib的动态代理代码 总结 1.概念 1.AOP技术简介 AOP 为Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一

  • 一文剖析JavaScript中闭包的难点

    目录 一.作用域基本介绍 1. 全局作用域 2. 函数作用域 3. 块级作用域 二.什么是闭包 1. 闭包的基本概念 2. 闭包产生的原因 3. 闭包的表现形式 三.如何解决循环输出问题 1. 利用 IIFE 2. 使用 ES6 中的 let 3. 定时器传入第三个参数 一.作用域基本介绍 ES6之前只有全局作用域与函数作用域两种,ES6出现之后,新增了块级作用域. 1. 全局作用域 在JavaScript中,全局变量是挂载在window对象下的变量,所以在网页中的任何位置你都可以使用并且访问到

随机推荐