C#中闭包的实现和注意事项详解

 更新时间:2025年01月02日 10:27:12   作者:啸猫先生  
闭包并不是某一个语言中特有的概念,在主流的编程语言中都有这个特性,闭包可以让一个内部方法可以访问它所在外部方法中的变量,并可以对变量的值进行修改,即使在外部方法的生命周期已经结束后,本文给大家介绍了C#中闭包的实现和注意事项,需要的朋友可以参考下

闭包的定义

闭包并不是某一个语言中特有的概念,在主流的编程语言中都有这个特性。闭包可以让一个内部方法可以访问它所在外部方法中的变量,并可以对变量的值进行修改,即使在外部方法的生命周期已经结束后。

不知道在看完上面这一串定义之后,可以明白闭包是个什么东西吗,反正我在一开始看完是不知道这是在说什么,不过没关系,我们可以先往下具体看闭包的一个例子。

C#中闭包的实现

下面是一个最简单的闭包,这里使用了委托和Lambda表达式,不过使用局部函数也是一样的效果,这两者之间的区别我们之后去讨论,这里就以使用委托的版本来看一下后台干了什么。

class Program
{
    // 使用委托和 Lambda 表达式
    static void Main(string[] args)
    {
        int num = 0;

        Action test = () =>
        {
            num++;
            Console.WriteLine(num);
        };
        test();
    }
}
class Program
{
    // 使用局部函数
    static void Main(string[] args)
    {
        int num = 0;
        Test();
        return;
        
        void Test()
        {
            num++;
            Console.WriteLine(num);
        }
    }
}

我们看构建后的低级别C#可以看出来,在Main方法中new了一个类,这个类中有一个num的成员变量,我们形成闭包的方法实际操作的是这个成员变量,这就解释了为什么外部方法的生命周期结束后,仍然可以访问到在其定义的变量。

闭包的注意事项

Lambda表达式和局部方法

我们先看下面这个场景,在这个场景我们使用了一个类中的某个方法并想要在完成后执行一个回调。

这里使用Lambda表达式和使用局部函数有两种情况,一是会捕获封闭范围内的变量时,如下面这段代码,这时使用局部函数和使用Lambda表达式一样,都会形成一个闭包,并使用类的成员变量来实现,如我在解释C#中闭包的实现时举得那个例子。

class Program
{
    static void Main(string[] args)
    {
        int num = 0;
        TestClass test = new TestClass();
        for (int i = 0; i < 3; i++)
        {
            test.DoSomething(() =>
            {
                num++;
                Console.WriteLine($"DoSomething finish {num}");
            });
        }
    }
}


class TestClass
{
    public void DoSomething(Action callback)
    {
        Console.WriteLine("DoSomething");
        
        // 执行回调
callback.Invoke();
    }
}

但是如果没有捕获任何变量时,如下面这种情况,可以看到使用局部函数的方式没有形成闭包,局部函数最终变成了一个静态函数。而使用委托和 Lambda 表达式的尽管没有捕获任何变量,但还是创建了一个类,虽然这个类只会实例化一次。

class Program
{
    // 使用局部函数,且没有捕获封闭范围内的变量
    static void Main(string[] args)
    {
        TestClass test = new TestClass();
        for (int i = 0; i < 3; i++)
        {
            test.DoSomething(DoSomethingCallBack);
        }
        return;
        
        void DoSomethingCallBack()
        {
            Console.WriteLine($"DoSomething finish");
        }
    }
}
class Program
{
    // 使用委托和 Lambda 表达式,且没有捕获封闭范围内的变量
    static void Main(string[] args)
    {
        TestClass test = new TestClass();
        for (int i = 0; i < 3; i++)
        {
            test.DoSomething(() =>
            {
                Console.WriteLine("DoSomething finish");
            });
        }
    }
}

配合for循环返回多个函数

闭包和for循环这也是一个经典的错误了,让我们来看下面这段代码,这里乍一看没什么问题,我们预期输出的结果是0,1。

class Program
{
    static void Main(string[] args)
    {
        int count = 2;
        Action[] fun = CreateActions(count);
        for (int i = 0; i < count; i++)
        {
            fun[i].Invoke();
        }
        return;

        Action[] CreateActions(int count)
        {
            Action[] actions = new Action[count];
            for (int i = 0; i < count; i++)
            {
                actions[i] = () => Console.WriteLine(i);
            }
            return actions;
        }
    }
}

但是我们实际运行一下,可以发现输出的结果是2,2。

这里我们看编译之后的代码,可以看出我们本质操控的其实是同一个<>c__DisplayClass0_0对象的成员变量i,所以自然输出都都是i自增之后的值。

这里改起来也比较容易,只需将for循环改成下面这样就行了。

for (int i = 0; i < count; i++)
{
    int j = i;
    actions[i] = () => Console.WriteLine(j);
}

这里要注意,int j = i;一定要在for循环里面,这样闭包才会创建3个不同的对象。如果像下面这样写,本质和最开始没有太大区别,还是操纵着一个对象。

int j;
for (int i = 0; i < count; i++)
{
    j = i;
    actions[i] = () => Console.WriteLine(j);
}

以上就是C#中闭包的实现和注意事项详解的详细内容,更多关于C#闭包实现和注意事项的资料请关注脚本之家其它相关文章!

相关文章

  • 利用C#快速查出哪些QQ好友空间屏蔽了自己

    利用C#快速查出哪些QQ好友空间屏蔽了自己

    我们经常会遇到以下情况吧:想点击好友空间看看他最近的动态,结果发现自己需要申请权限!别担心,本文将为大家介绍如何利用C#快速查出哪些QQ好友空间屏蔽了自己,需要的可以参考一下
    2022-02-02
  • Windows Form 分页 具体实现

    Windows Form 分页 具体实现

    其实功能实现很简单。我做的是一个通用的分页控件。项目时间很紧,可能有点粗糙。欢迎大家斧正。不说了直接贴代码吧
    2013-12-12
  • C#使用linq对数组进行筛选排序的方法

    C#使用linq对数组进行筛选排序的方法

    这篇文章主要介绍了C#使用linq对数组进行筛选排序的方法,实例分析了C#实用linq扩展进行数组排序的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • C#实现插入排序算法实例

    C#实现插入排序算法实例

    这篇文章主要介绍了C#实现插入排序算法,实例分析了插入排序算法的原理与实现技巧,需要的朋友可以参考下
    2015-05-05
  • C#遍历得到checkboxlist选中值和设置选中项的代码

    C#遍历得到checkboxlist选中值和设置选中项的代码

    这篇文章主要介绍了C#遍历得到checkboxlist选中值和设置选中项的代码,代码简单易懂,具有参考借鉴价值,需要的朋友可以参考下
    2016-08-08
  • C#线程执行超时处理与并发线程数控制实例

    C#线程执行超时处理与并发线程数控制实例

    这篇文章主要介绍了C#线程执行超时处理与并发线程数控制的方法,实例讲述了并发执行存储过程的最大个数,读者可对程序稍做改动即控制并发线程数,具有一定的参考借鉴价值,需要的朋友可以参考下
    2014-11-11
  • C#中静态方法和实例化方法的区别、使用

    C#中静态方法和实例化方法的区别、使用

    这篇文章主要介绍了C#中静态方法和实例化方法的区别、使用,文中讲解的非常细致,对大家的学习有所帮助,感兴趣的朋友可以了解下
    2020-06-06
  • C#实现把图片转换成二进制以及把二进制转换成图片的方法示例

    C#实现把图片转换成二进制以及把二进制转换成图片的方法示例

    这篇文章主要介绍了C#实现把图片转换成二进制以及把二进制转换成图片的方法,结合具体实例形式分析了基于C#的图片与二进制相互转换以及图片保存到数据库的相关操作技巧,需要的朋友可以参考下
    2017-06-06
  • C#计算两个文件的相对目录算法的实例代码

    C#计算两个文件的相对目录算法的实例代码

    现在已知两个文件相对于网站根目录的路径,如何计算相对路径呢,有需要的朋友可以参考一下
    2013-09-09
  • C#中感叹号(!) 的作用总结

    C#中感叹号(!) 的作用总结

    这篇文章主要给大家总结介绍了C#中感叹号(!) 的作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12

最新评论