C#/.NET 匿名函数会捕获变量,并延长对象的生命周期
2018-01-30 08:30
295 查看
小伙伴在一次垃圾回收中,发现对象并没有被回收掉,而注释掉一句代码后它便能够回收。
这究竟是为什么?
不关心探索过程的就直接拉到最后看结论吧!
需要验证的是
由于
于是,我们将最后一行换成别的函数别的参数:
或者将整个这一句提取成新的函数:
那么,回收就会正常进行。
现在,不执行这个受争议的函数了,我们使用空的匿名函数。
一样会导致不回收。
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.
匿名函数会捕获当前上下文的局部变量,延长对象的生命周期;直到此委托或表达式树被回收掉。
也就是说,只要某个方法中存在没有被回收的匿名函数/lamda 表达式/表达式树,那么当前上下文的对象直到这些匿名函数被回收之前都不会被回收,即便已经设为了 null。
C# Language Specification 5.0
C# 6.0 draft Language Specification - Microsoft Docs
这究竟是为什么?
不关心探索过程的就直接拉到最后看结论吧!
探索
测试代码是这样的: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.
匿名函数会捕获当前上下文的局部变量,延长对象的生命周期;直到此委托或表达式树被回收掉。
也就是说,只要某个方法中存在没有被回收的匿名函数/lamda 表达式/表达式树,那么当前上下文的对象直到这些匿名函数被回收之前都不会被回收,即便已经设为了 null。
参考资料
c# - .NET Do lambdas prevent garbage collection of external references used in them? - Stack OverflowC# Language Specification 5.0
C# 6.0 draft Language Specification - Microsoft Docs
相关文章推荐
- 深入理解 c# 第五章 捕捉变量的生命周期延长的例子
- C#2008与.NET 3.5 高级程序设计读书笔记(8)--对象的生命周期
- [翻译]理解C#对象生命周期
- C# 线程手册 第二章 .NET 中的线程 线程的生命周期
- 在.net cf(C#)中捕获输入法面板(InputPanel&SIP)高度改变事件
- 一起谈.NET技术,C#面向对象设计模式纵横谈:Singleton 单件
- C#中将值类型变量赋值给对象与将对象赋值给值类型变量(装箱与拆箱)
- .net对象生命周期与内存小结
- c# 把一个匿名对象赋值给一个Object类型的变量后,怎么取这个变量? c# dynamic动态类型和匿名类 详解C# 匿名对象(匿名类型)、var、动态类型 dynamic 深入浅析C#中的var和dynamic
- 步步为营VS 2008 + .NET 3.5(3) - C# 3.0新特性之Automatic Properties(自动属性)、Object Initializers(对象初始化器)、Collection Initializers(集合初始化器)和Ext
- .Net(C#)开发漫谈:关于变量的命名和属性
- .NET (C#) Internals: ASP.NET 应用程序与页面生命周期(意译)
- c# 类和对象的区别及联系,对象和变量是一回事吗?有区别吗?
- (转载)理解C#对象生命周期
- Serializable在C#中的作用.NET 中的对象序列化
- Serializable在C#中的作用.NET 中的对象序列化
- Serializable在C#中的作用.NET 中的对象序列化
- .NET (C#) Internals: ASP.NET 应用程序与页面生命周期
- [Serializable]在C#中的作用,.NET 中的对象序列化
- 基于ASP.net C#技术来实现,介绍如何处理Session对象变量失效的问题