C#清零前清空列表

C# Clearing list before nulling

今天看到一段代码,乍一看觉得很奇怪,让我重新考虑。这是代码的简化版本:

if(list != null){
    list.Clear();
    list = null;
}

我的想法是,为什么不将其简单地替换为:

list = null;

我读了一点书,我知道清除列表会删除对允许 GC 执行它的对象的引用,但不会 "resize"。此列表的分配内存保持不变。

另一方面,设置为 null 也会删除对列表的引用(并因此删除其项目),同时允许 GC 执行它。

所以我一直在试图找出一个理由,就像第一个街区一样。我想到的一种情况是,如果您对列表有两个引用。第一个块将清除列表中的项目,因此即使第二个引用仍然存在,GC 仍然可以清除为项目分配的内存。

不过,我觉得这有什么奇怪的,所以我想知道我提到的场景是否有意义?

此外,是否还有任何其他情况需要我们在将引用设置为 null 之前立即清除 () 列表?

最后,如果我提到的场景有意义,那么确保我们不会同时持有对该列表的多个引用以及我们将如何(明确地)做到这一点不是更好吗?

编辑:我明白了清除列表和清空列表的区别。我主要想知道 GC 内部是否有某些东西可以使它成为 Nulling 之前清除的理由。

清除列表可以确保如果列表由于某种原因没有被垃圾回收,那么至少,它包含的元素仍然可以被处理掉。

如评论中所述,防止对列表的其他引用存在需要仔细规划,并且在清空列表之前清除列表不会导致足够大的性能损失以证明试图避免这样做是合理的。

docs所述:

List.Clear Method (): Count is set to 0, and references to other objects from elements of the collection are also released.

在您的第一个片段中:

if(list != null){
    list.Clear();
    list = null;
}

如果你只是将list设置为null,这意味着你将list的引用释放到内存中的实际对象(所以list 本身保留在内存中)并等待垃圾收集器来释放其分配的内存。

但是问题是您的列表可能包含包含对另一个对象的引用的元素,例如:

list → objectA, objectB, objectC
objectB → objectB1, objectB2

所以,在将list设置为null后,现在列表没有引用,稍后应该由垃圾收集器收集,但是objectB1objectB2 有一个来自 objectB 的引用(仍然在内存中),正因为如此,垃圾收集器需要分析对象引用链。为了减少混淆,此代码段使用 .Clear() 函数来消除这种混淆。

这取决于您使用的 .NET 版本。在 Xamarin 或 mono 等移动平台上,您可能会发现垃圾收集器需要这种帮助才能完成其工作。而在桌面平台上,垃圾收集器的实现可能更加复杂。 CLI 的每个实现都将有它自己的垃圾收集器实现,并且它的行为可能因一个实现而异。

我记得 10 年前,我在开发一个 Windows 移动应用程序,该应用程序存在内存问题,而这种代码就是解决方案。这可能是由于移动平台需要比桌面更节俭处理能力的垃圾收集器。

解耦对象有助于简化垃圾收集器需要进行的分析,并有助于避免垃圾收集器无法识别大量对象实际上已与应用程序中的所有线程断开连接的情况。这会导致内存泄漏。

任何认为 .NET 中不会有内存泄漏的人都是没有经验的 .NET 开发人员。在桌面平台上,只需确保在实现它们的对象上调用 Dispose 就足够了,但是对于其他实现,您可能会发现它不是。

List.Clear() 会将列表中的对象与列表以及彼此解耦。

编辑: 所以要明确一点,我并不是说目前存在的任何特定实现都容易发生内存泄漏。再次取决于何时阅读此答案,垃圾收集器在当前 CLI 的任何实现上的稳健性可能自编写本文时起已经发生变化。

基本上我是在建议,如果您知道您的代码需要跨平台并在 .NET 框架的许多实现中使用,尤其是移动设备的 .NET 框架实现,那么可能值得投入时间不再需要时解耦对象。在那种情况下,我会首先向已经实现 Dispose 的 类 添加解耦,然后如果需要的话查看实现 IDisposable on classes that don't implement IDisposable 并确保在那些 类.[=12 上调用 Dispose =]

如何确定是否需要?您需要检测和监控您的应用程序在每个要部署的平台上的内存使用情况。与其编写大量多余的代码,我认为最好的方法是等到您的监控工具指示您有内存泄漏。

list.Clear() 在您的场景中不是必需的(其中 Listprivate 并且仅在 中使用 class).

关于可达性/活动对象的一个​​很好的介绍级别 link 是 http://levibotelho.com/development/how-does-the-garbage-collector-work :

How does the garbage collector identify garbage?

In Microsoft’s implementation of the .NET framework the garbage collector determines if an object is garbage by examining the reference type variables pointing to it. In the context of the garbage collector, reference type variables are known as “roots”. Examples of roots include:

  • A reference on the stack
  • A reference in a static variable
  • A reference in another object on the managed heap that is not eligible for garbage collection
  • A reference in the form of a local variable in a method

此上下文中的关键位是托管堆上不符合垃圾回收条件的另一个对象中的引用。因此,如果 List 符合收集条件(并且列表中的对象未在别处引用),则列表中的那些对象也符合收集条件。

换句话说,GC会意识到list和它的内容在同一个pass中是不可达的

那么,有没有 list.Clear() 有用的例子?是的。如果您有两个对单个 List 的引用(例如,作为两个不同对象中的两个字段),这可能很有用。其中一个参考文献可能希望以其他参考文献也受到影响的方式清除列表 - 其中 list.Clear() 是完美的。

此回答最初是对 的评论,他声称 :

It depends on which version of .NET you are working with. On mobile platforms like Xamarin or mono, you may find that the garbage collector needs this kind of help in order to do its work.

该声明需要进行事实核查。那么,让我们看看...


.NET

.NET 使用分代标记和清除垃圾收集器。您可以在 What happens during a garbage collection 中查看算法的摘要 。总而言之,它会遍历对象图,如果它无法到达某个对象,则可以删除该对象。

因此,垃圾收集器将在同一迭代中正确地将列表项识别为可收集项,无论您是否清除列表。不需要事先解耦对象。

这意味着清除列表不会帮助垃圾收集器正常执行 .NET。

注意:如果有另一个对该列表的引用,那么您清除列表的事实将是可见的。


Mono 和 Xamarin

单声道

事实证明,Mono也是如此。

Xamarin.Android

Xamarin.Android也是如此。

Xamarin.iOS

但是,Xamarin.iOS 需要额外考虑。特别是,MonoTouch 将使用 wrapped Objective-C objects which are beyond the garbage collector. See Avoid strong circular references under iOS Performance。这些对象需要不同的语义。

Xamarin.iOS 将通过保留缓存来最小化 Objetive-C 对象的使用:

C# NSObjects are also created on demand when you invoke a method or a property that returns an NSObject. At this point, the runtime will look into an object cache and determine whether a given Objective-C NSObject has already been surfaced to the managed world or not. If the object has been surfaced, the existing object will be returned, otherwise a constructor that takes an IntPtr as a parameter is invoked to construct the object.

即使没有来自托管代码的引用,系统也会使这些对象保持活动状态:

User-subclasses of NSObjects often contain C# state so whenever the Objective-C runtime performs a "retain" operation on one of these objects, the runtime creates a GCHandle that keeps the managed object alive, even if there are no C# visible references to the object. This simplifies bookeeping a lot, since the state will be preserved automatically for you.

强调我的。

因此,在 Xamarin.iOS 下,如果列表可能包含包装的 Objetive-C 对象,此代码将有助于垃圾收集器。

看到问题How does memory management works on Xamarin.IOS, Miguel de Icaza explains in his answer,语义是在引用时“保留”对象和“释放[=92=” ]" 当引用为空时它。

在Objetive-C这边,"release"并不意味着销毁对象。 Objetive-C 使用引用计数垃圾收集器。当我们“retain”对象时,计数器递增,当我们“release”时,计数器减少。当计数器达到零时,系统会销毁该对象。参见:About Memory Management.

因此,Objetive-C不擅长处理循环引用(如果A引用BB引用A,它们的引用计数不为零,即使如果无法到达),因此,您应该在 Xamarin.iOS 中避免使用它们。事实上,忘记解耦引用会导致 Xamarin.iOS 中的泄漏...参见:Xamarin iOS memory leaks everywhere.


其他

dotGNU 还使用分代标记和清除垃圾收集器。

我也看过 CrossNet(将 IL 编译成 C++),看来他们也试图实现它。不知道好不好