在 .NET List 中将项目设置为 null 是否使其可用于垃圾回收,这是一个好主意

本文关键字:用于 一个 好主意 NET List 项目 是否 null 设置 | 更新日期: 2023-09-27 18:36:22

假设我有一个大列表,其中每个项目只处理一次,然后在长时间的操作中再也不会查看:

List<T> items;
 // ... some stuff is done with the list then finally
 for(int i = 0; i < items.Count; i++)
 {
     SomeOperation(items[i]);
     //items[i] never used again at this point
     // say i do this:
     // items[i] = null;
 }

如果我取消注释items[i] = null,这会取消索引 i 处的对象并使其可用于垃圾回收吗?

从内存使用的角度来看,这样做是否比让 GC 稍后在整个列表未使用时发生更有效。

这忽略了一些问题,例如稍后的代码更改,事实证明这些项目稍后使用,并且意外的空残骸造成严重破坏。

在 .NET List<T> 中将项目设置为 null 是否使其可用于垃圾回收,这是一个好主意

如果对该

对象的唯一根可访问引用是通过该列表,那么是的,将该引用设置为 null 将使该对象符合垃圾回收的条件

当垃圾回收最终发生时,如果该对象最终被清理,而列表本身需要保留,那么您将减少应用程序的内存占用量,该对象的大小。

请注意,为引用列表中的对象分配的内存仍然存在;只有可以清理它引用的对象的内存。

正如许多其他人所提到的,您对这种数据结构的使用强烈表明您首先不应该使用List,而应该使用Queue或其他类似的数据结构,因为这会更有效地适合您的使用。

你还需要记住,记忆是廉价的;人们往往拥有很多记忆。 除非你有成千上万的物体可以长时间保持活力,否则这不太可能是一个足以让你注意到它的改进;您甚至可能无法测量差异。 使用正确的数据结构将为您带来所有这些好处,甚至更多,并使代码大大简化,更具可读性/可维护性。

是的

,如果列表最后引用对象设置元素以给定索引为 null,则对象符合 GC 的条件。

这不是一个好主意:

  • 代码看起来很奇怪。可能好的评论为什么这样做会有所帮助。
  • 可能有更好的方法来表达这种行为。

如果您尝试将元素设置为 null 并且分析显示性能/可伸缩性/您正在寻找的任何内容的可衡量增长 - 请考虑重写代码以使用其他更合适的结构来更好地表达列表的临时性质(可能是队列、堆栈或只是带有惰性计算的 IEnumrable)

当它可能有益时(确保剖析以证明):

  • 列表包含大型托管对象,如位图或内存流。在这种情况下,GC 很有可能实际自动运行和清理对象(假设在迭代期间有更多的分配)
  • list 包含包含包含大型非托管对象(
  • 如全局分配的内存句柄)的小型托管对象 - 设置为 null + 正确 释放 + 强制 可能需要 GC 来触发早期清理(GC 不会检测到非托管内存压力)
  • list 用作某些数据结构(如自定义堆栈、队列或循环缓冲区)的后备存储,作为您自己的库的一部分 - 将元素设置为 default(T)/null 是防止自定义数据结构延长类型不再使用的对象的生存期的明智方法。

请注意,在实现自定义数据类型的情况下,由于数据类型的一般协定而不是由于分析,您可能必须这样做 - 即预计 Stack 在"Pop"之后完全忘记对象。代码应该在internalBuffer[i]=null;旁边有适当的注释 - 即"避免在对象从 Stack 中删除后保留引用"。

如其他部分所述,设置为 null 应该允许对对象进行垃圾回收。

你在评论中说

我考虑过使用另一种数据结构,如队列,但这在其他代码的上下文中似乎并不自然,而列表确实如此。

您始终可以从列表中初始化Queue

List<T> list = GetList<T>();
Queue<T> queue = new Queue<T>(list); // O(n)
list.Clear(); // to free up memory as per requirement

CLR 探查器可用于查找分配给函数、类和程序集的内存,以评估性能。

一些参考资料:

.NET 最佳实践编号:1:- 检测 .NET 代码中的高内存消耗函数

使用 CLR 探查器监视 .NET 中的垃圾回收活动