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 条评论
登录 后参与评论

相关文章

来自专栏偏前端工程师的驿站

Design Pattern: Not Just Mixin Pattern

Brief                                 从Mix-In模式到Mixin模式,中文常用翻译为“混入/织入模式”。单纯从名字上看...

1976
来自专栏java一日一条

关于 hashCode() 你需要了解的 3 件事

在 Java 中,每一个对象都有一个容易理解但是仍然有时候被遗忘或者被误用的 hashCode 方法。这里有3件事情要时刻牢记以避免常见的陷阱。

592
来自专栏web编程技术分享

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

40612
来自专栏Android开发实战

设计模式-访问者模式

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

1163
来自专栏点滴积累

shapeless官方指南翻译写在前面

目录 前言 Shapeless简介 The Type Astronaut's Guide to Shapeless简介 总结 一、前言        在我的20...

3657
来自专栏java一日一条

Java 8新的时间日期库的20个使用示例

除了lambda表达式,stream以及几个小的改进之外,Java 8还引入了一套全新的时间日期API,在本篇教程中我们将通过几个简单的任务示例来学习如何使用J...

1202
来自专栏ACM算法日常

魔咒词典(hash表)- HDU 1880

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

902
来自专栏数据库

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

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

1909
来自专栏我的小碗汤

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

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

1362
来自专栏为数不多的Android技巧

一种绕过Android P对非SDK接口限制的简单方法

众所周知,Android P 引入了针对非 SDK 接口(俗称为隐藏API)的使用限制。这是继 Android N上针对 NDK 中私有库的链接限制之后的又一次...

4982

扫码关注云+社区