C#闭包最通俗易懂的解释

目录

闭包概念

闭包案例

使用方法和好处

存在的隐患

内存泄漏

闭包陷阱


闭包概念

闭包是指捕获(引用)了作用域外的函数的局部变量的函数。

由于外部函数的局部变量被捕获,即使外层函数执行已终止,内部定义的函数也可以访问外部函数的局部变量,所以局部变量的生命周期得到了延长。

闭包本质是一个对象(编译后) 。

当定义一个闭包时,编译器会生成一个类,这个实例就是闭包对象,这个类包含了闭包所引用的外部变量和代码块。当调用闭包时,实际上是在调用这个类的实例

在C#中我们可以使用委托Lambda表达式来实现闭包,闭包使用上和方法一致。

闭包案例

public class Test
{
    public Action action;
    static void Main(string[] args)
    {
        F1();
        action();
    }
    //外部函数F1
    public void F1()
    {
        //定义局部变量a
        int a = 1;

        //内部定义嵌套函数F2
        void F2()
        {
            a += 1;
            Console.WriteLine(a);
        }
        //订阅函数F2
        action += F2;

        //内部定义嵌套函数F3
        void F3()
        {
            a += 1;
            Console.WriteLine(a);
        }
        //订阅函数F3
        action += F3;

        //再订阅Lambda表达式匿名函数
        action += () =>
        {
            a += 0;
            Console.WriteLine(a);
        };
    }
}

执行结果为 2,3,3

过程解析:

内部定义的函数F2函数F3匿名函数都捕获了外部函数F1局部变量a,并将三个方法依次订阅action委托并调用

PS:

由于三个内部定义函数都是直接引用a,所以捕获的是a的内存地址,修改会影响到a本身。

即使外层函数F1执行已终止,内部定义的三个函数也可以访问外层函数F1的局部变量a,所以局部变量a生命周期得到了延长。

结论:

第一个结果为函数F2执行的a += 1; 此时a为1 + 1 = 2

第二个结果为函数F3执行的a += 1; 此时a为2 + 1 = 3

第三个结果为匿名函数执行的a += 0; 此时a为3 + 0 = 3

使用方法和好处

  • 用于创建私有变量,限制外部对变量的直接访问和修改,提高代码的封装性
  • 用来模拟私有方法,将数据隐藏和封装
  • 可以重复使用变量,并且不会造成变量污染
  • 实现回调函数时常常会使用闭包,将函数和相关的数据引用打包在一起,方便在特定事件发生时调用。

存在的隐患

内存泄漏

变量长期驻扎在内存中,可能导致内存泄漏。如果闭包不再需要,可以手动释放,比如将闭包设置为null,以便让垃圾回收器回收。

闭包陷阱

以下是一个常见的闭包陷阱例子:

Action<int> actions = new Action<int>();
for (int i = 0; i < 5; i++)
{
    actions += (() =>
    {
        Console.WriteLine(i)
    });
}
foreach (int i in actions)
{
    Console.WriteLine(i);
}

首先创建了一个委托,然后for循环里的5个匿名函数都捕获了循环变量i,因此形成了闭包

当我们执行这个程序时,我们期望输出的是数字1到5,但实际上输出的是数字5,5,5,5,5。这是因为每个匿名函数都引用了同一个变量i的内存地址,而这个变量的值在循环结束后变成了5。所以当我们调用这些匿名函数时,它们都输出变量i的最终值5。

为了避免这个陷阱,我们可以在每次循环时定义一个新的局部变量j,将循环变量i的值复制到局部变量j中,然后让匿名函数捕获(引用)局部变量j。这样,每个匿名函数都引用的是新定义的不同的变量j,而不同一个循环变量i。以下是修改后的代码:

Action<int> actions = new Action<int>();
for (int i = 0; i < 5; i++)
{
    //局部变量j
    int j = i;
    //Lambda简写,省略了花括号
    actions+=(() => Console.WriteLine(j));
}
foreach (int i in actions)
{
    Console.WriteLine(i);
}

当我们再次调用这些匿名函数时,结果就是期望的1,2,3,4,5。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值