C#/.NET 匿名函数会捕获变量,并延长对象的生命周期

C#/.NET 匿名函数会捕获变量,并延长对象的生命周期

发布于 2018-01-05 01:26 更新于 2018-09-01 00:14

小伙伴在一次垃圾回收中,发现对象并没有被回收掉,而注释掉一句代码后它便能够回收。

这究竟是为什么?


不关心探索过程的就直接拉到最后看结论吧!

探索

测试代码是这样的:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var variable = new MainPage();
    var reference = new WeakReference<MainPage>(variable);
    variable = null;
    GC.Collect();
    Console.WriteLine($"{reference.TryGetTarget(out var target)}: {target}");
    DoSomething(x => DoAnotherThing(x));
}

需要验证的是 MainPage 对象是否被回收。然而在这段代码中,MainPage 并没有被回收;然而去掉最后一行,MainPage 便可以正常回收。关键是,即便是在 Console.WriteLine 上打下断点,让代码永远不会执行到最后一句,也不会改变回收的结果。

由于 DoSomething 中的委托参数恰好就是 MainPage 类型的,不禁让人觉得可能是此函数做了一些奇怪的事情。然而毕竟参数中传入的委托参数只是形参,理论上不应该影响到外部对象的回收。那么影响的只可能是变量的捕获了。

于是,我们将最后一行换成别的函数别的参数:

DoSomething(null);

或者将整个这一句提取成新的函数:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    // 省略前面的代码。
    ExtractedMethod();
}

private void ExtractedMethod()
{
    DoSomething(x => DoAnotherThing(x));
}

那么,回收就会正常进行。

现在,不执行这个受争议的函数了,我们使用空的匿名函数。

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var variable = new MainPage();
    var reference = new WeakReference<MainPage>(variable);
    variable = null;
    GC.Collect();
    Console.WriteLine($"{reference.TryGetTarget(out var target)}: {target}");
    Dispatcher.InvokeAsync(() => { });
}

一样会导致不回收。

结论

在微软官方的《C# 规范 5.0》(点此下载)的第 7.15.5.1 章节中有说到:

When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function. Ordinarily, the lifetime of a local variable is limited to execution of the block or statement with which it is associated (§5.1.7). However, the lifetime of a captured outer variable is extended at least until the delegate or expression tree created from the anonymous function becomes eligible for garbage collection.

匿名函数会捕获当前上下文的局部变量,延长对象的生命周期;直到此委托或表达式树被回收掉。

也就是说,只要某个方法中存在没有被回收的匿名函数/lambda 表达式/表达式树,那么当前上下文的对象直到这些匿名函数被回收之前都不会被回收,即便已经设为了 null。


参考资料

本文会经常更新,请阅读原文: https://walterlv.com/post/anonymous-function-extends-lifetime-of-variable.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏web编程技术分享

【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第六节)

446120
来自专栏码洞

老板们都应该学一学 Redis,它能管理上亿对象,你们呢?

我们知道一个大型的公司往往都具有复杂的组织结构,成百上千号员工,要做到大而不乱,就必须依靠合理的组织结构来优化内部的交流成本。Redis 内部也有组织结构,不同...

18850
来自专栏NetCore

[原创]Fluent NHibernate之旅(三)-- 继承

经过了“开篇”和“简单映射”两篇文章,相信大家对Fluent NHibernate 有了一定的了解了,FluentNHibernate实际就是对 NHibern...

20580
来自专栏ACM算法日常

魔咒词典(hash表)- HDU 1880

公众号现在输入题号可以直接查看题目啦~比如输入1000,会显示HDU1000的题目内容

10720
来自专栏数据库

httpclient如何快速往数据库里添加测试用例

本人在使用httpclient做接口测试的过程中,使用数据库管理用例,其中存的key-value的形式,由于接口的参数可能比较多,所以一个个用例写起来会比较麻烦...

21290
来自专栏互联网高可用架构

白话阿里巴巴Java开发手册(编程规约)

38530
来自专栏斑斓

Scala的面向对象与函数编程

很难说FP和OO孰优孰劣,应该依场景合理选择使用。倘若从这个角度出发,Scala就体现出好处了,毕竟它同时支持了OO和FP两种设计范式。 从设计角度看,我认为O...

31950
来自专栏我的小碗汤

为什么在Go语言中要慎用interface{}

在掘金上看到一篇从java转Go思想上的变化以及对go语言思考的文章,写的很透彻,我也推敲了一遍。这里也分享给大家,或许对将要或者已经学习golang的同学有所...

17120
来自专栏Java爬坑系列

【JAVA零基础入门系列】Day14 Java对象的克隆

  今天要介绍一个概念,对象的克隆。本篇有一定难度,请先做好心理准备。看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充。   克隆...

22860
来自专栏Android开发实战

设计模式-访问者模式

访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个...

13030

扫码关注云+社区

领取腾讯云代金券