【问题标题】:What are the rules for when dispose() is required?需要 dispose() 时的规则是什么?
【发布时间】:2016-05-23 23:52:02
【问题描述】:

虽然我已经编码了一段时间,但我真的只是勉强进入我所说的中级编码器。所以我理解了dispose()的原理,就是释放为变量和/或资源保留的内存。我还发现有时使用 EF 我必须 dispose() 才能使其他操作正常工作。我不明白的正是需要发布的内容,何时使用 dispose()。

例如,我们不会处理字符串、整数或布尔值等变量。但是在某处我们越过“一条线”,我们使用的变量和/或资源需要被处理掉。我不明白线路在哪里。

在知道何时使用 dispose() 时,是否有一个单一的原则或几个广泛的原则可以应用?

我阅读了这些 SO 帖子(a specific situationmore about how rather than when),但我觉得我不了解知道何时使用 dispose() 的基础知识。我看到的一条评论询问当变量超出范围时是否释放内存,这引起了我的注意,因为在我看到响应为否之前,它不会因为超出范围而被释放,我本来以为当它超出范围时它会被释放。我不想成为第二个链接中的一个人所说的“毫无头绪的开发人员”,尽管我认为这有点苛刻。我们中的一些人仍在学习。

所以这就是为什么我的问题是“什么决定何时真正需要 dispose()?”

我的问题不是如何,而是何时。当然 cmets 知道如何有用,但是即使调用 dispose() 的方法是 Using 语句,我仍然需要知道什么时候。

编辑原始问题:我知道这是一个很长的解释,因为标记为重复的评论注释请求,这不是咆哮,我只是不知道如何确保我将注意力集中在我的精确问题上。很多时候,我们只是因为我们提出问题的方式而绊倒。正如我在这篇长文末尾提到的那样,在我们专注于我的问题之后,我将编辑所有这些,假设我们到达那里。根据我的阅读,我认为这是一个重要的问题。

提议的“回答”帖子是一篇很棒的帖子,但并没有真正回答我的问题。 CodeNotFound 下面的评论也提供了一个 great 链接,但它也没有真正回答我的问题。我提供了有关这些帖子的 cmets,以尝试帮助完善我的精确问题:

When should I dispose my objects in .NET?:第一个答案以评论开头

一次性对象表示持有 CLR 本质上不知道的有价值资源的对象。

不幸的是,我不明白“一次性对象...... CLR 本质上不知道”一词包括什么。 这就是我要问的。我如何知道某物是否属于我必须处置的类别?我们一直在定义要在代码中使用的东西。我们什么时候越界,它就变成了我需要 dispose() 的对象?顺便说一句,我注意到该帖子的作者从未标记答案。我不知道这是否意味着他觉得这个问题没有得到回答,或者只是他的跟进很差,但希望我已经对我希望理解的内容进行了一些改进。当您仔细查看答案时,它们并没有真正解决 哪些 对象需要开发人员操作来处理它们的问题,或者我可能如何知道如何识别 对象。我只是不知道我创建的对象或事物需要我负责处理。我知道 GC 和其他规定开始发挥作用,但同样,这只是如何。似乎很清楚的是,大多数经验丰富的专业开发人员都知道何时需要处理他们创建的东西。我不明白如何知道那个

Proper use of the IDisposable interface:显然是一个受欢迎的答案(1681 次投票),但标记的答案以

开头

Dispose 的目的是释放非托管资源”。

好的,但我的问题是我怎么知道它是一个非托管资源?而且我不明白下面的注释如何适用于需要处理的什么

如果您在 .NET 框架中找到它,它是托管。如果您自己浏览 MSDN,它是不受管理的...您现在负责清理它。”

我不明白如何使用这种类型的解释来分类我需要 dispose() 和不需要的内容。 .net 框架中有各种各样的东西;如何分离出需要我 dispose() 的东西?我看什么来告诉我我对此负责?

在那之后,答案继续详细说明如何处置(),但我仍然坚持什么需要处置。为了让我的话题更加复杂,那位作者后来说:“所以现在我们将......

摆脱非托管资源(因为我们必须这样做),

摆脱托管资源(因为我们希望提供帮助)

所以现在我需要考虑处理一组全新的使用内存的对象,我也不知道它们是什么。答案的作者后来说

对于任何喜欢这个答案风格的人(解释原因,所以如何变得显而易见)......

我知道作者是在建议其他文章,但作者认为理解“为什么”使“如何”变得显而易见的建议并不真正合理,因为对一个人显而易见的事情对其他人并不总是显而易见的。即便如此,作者更多地关注为什么和如何,而我的问题是关于何时,这意味着什么需要处理( ),而不是 when 我完成了它。我知道什么时候我已经完成了一些事情,我只是不知道哪些我要负责什么时候我已经完成了他们。

对于大多数开发人员什么需要处理()来说可能是显而易见或本能的,但对我来说并不明显,我相信在我的经验阶段还有很多其他人,我希望就什么进行更集中的对话。当然 why 很有用,但在这种情况下,只有当 why 附加到 what 时。例如:您必须释放 DbContext 因为 CLR 不会释放它 - 因为 解释了为什么,但在这种情况下,它是DbContext 是必须处理的 what

我希望有一个关于必须处理什么的一般原则,而不是一长串具体项目,这对于像我这样正在寻找简单指南的人来说不是特别有用。 p>

再一次,我知道释放内存很重要,而且很多经验和专业知识都用于学习为什么如何,但我仍然离开难以理解需要处理的什么。一旦我理解了什么我必须处理(),然后我就可以开始努力学习如何去做。

所以这仍然是一个糟糕的问题吗?假设我们能够更加专注于我所问的内容,我稍后会编辑所有这些解释以使帖子更加简洁。

最终编辑:虽然我上面说过我会编辑掉我最初认为问题中不必要的文本,但我认为最好保留它。我认为提出问题的方式已经帮助我们理解答案的潜力。即使答案永远不会改变,但如果我们不将答案与我们在脑海中构建问题的方式联系起来,我们可能不会真正理解答案。因此,如果这个问题的构架方式与某人有关,我鼓励您完整阅读标记为答案的帖子以及 cmets。虽然最后的答案非常简单,但有很多历史和背景对于理解这个问题的答案很重要。为清楚起见,在有关 dispose() 的讨论的整个过程中,答案也被反复编辑。享受...

【问题讨论】:

  • 如果您的类型实现了 IDisposable,请使用 Dispose
  • dispose 是一种确定性清理,其中 GC 不是确定性的。
  • “所以我明白了dispose()的原理,就是释放为变量和/或资源保留的内存。”这正是 Dispose 不适合的。它是释放需要释放的东西,这些东西是托管内存。
  • @Alan:开发人员倾向于在使用某个概念之前对其进行详细介绍。如果您打算*实现`IDisposable,这是必要的(但您可能不需要这样做)。如果你打算使用实现IDisposable的对象,Eric Lippert's answer就足够了。

标签: c# .net vb.net entity-framework dispose


【解决方案1】:

我理解 dispose() 的原理,就是释放为变量和/或资源保留的内存。

您确实了解处置的目的。 不是用于释放与变量相关的内存。

我不明白的正是需要发布的内容,何时使用 dispose()。

当你确定你已经完成了任何实现 IDisposable 的东西。

例如,我们不会处理字符串、整数或布尔值等变量。但是在某处我们越过“一条线”,我们使用的变量和/或资源需要被处理掉。我不明白线路在哪里。

这条线是为你划定的。当一个对象实现 IDisposable 时,它​​应该被释放。

我注意到 变量 根本不是可处置的东西。 对象 被释放。对象不是变量,变量也不是对象。变量是的存储位置。

当知道何时使用 dispose() 时,是否有一个单一的原则或几个广泛的原则可以应用?

一个单一的原则:当对象是一次性的。

我觉得我不了解知道何时使用 dispose() 的基本知识。

丢弃所有一次性物品。

我看到的一条评论询问当变量超出范围时是否释放内存,这引起了我的注意,因为在我看到响应为否之前,它不会因为超出范围而被释放,我会认为当它超出范围时它确实会被释放。

谨慎使用语言。您混淆了范围和生命周期,并且混淆了变量与变量的内容。

首先:变量的范围是程序文本的区域,在该区域中可以通过名称引用该变量。变量的生命周期是在程序执行期间变量被认为是垃圾收集器的根的时间段。作用域纯粹是一个编译时概念,生命周期纯粹是一个运行时概念。

范围和生命周期之间的联系是,局部变量的生命周期通常从控制进入变量范围时开始,并在离开时结束。但是,各种事情都可以改变本地的生命周期,包括被关闭、在迭代器块中或在异步方法中。抖动优化器还可以缩短或延长本地的寿命。

还请记住,变量是存储,它可能指的是存储。当本地的生命周期结束时,与 本地 关联的存储可能会被回收。但是,无法保证与本地引用的事物相关的存储在当时或以后会被回收。

所以这就是为什么我的问题是“什么决定何时真正需要 dispose()?”

当对象实现 IDisposable 时,Dispose 是必需的。 (有少量不需要处理的一次性对象。例如任务。但作为一般规则,如果它是一次性的,则将其处理掉。)

我的问题不是如何,而是何时。

只有在你完成后才处置它。不是以前,也不是以后。

我应该什么时候在 .NET 中处理我的对象?

在对象实现 IDisposable 时将其释放,并且您已完成使用它们。

我如何知道某物是否属于我必须处置的类别?

在实现 IDisposable 时。

我只是不知道我创建的哪些对象或事物需要我负责处理。

一次性的。

大多数经验丰富的专业开发人员都知道何时需要处理他们创建的内容。我不明白怎么知道。

他们检查对象是否是一次性的。如果是,他们将其丢弃。

Dispose 的目的是释放非托管资源”。好的,但我的问题是我如何通过查看某个东西知道它是非托管资源?

它实现了 IDisposable。

我不明白如何使用这种类型的解释来分类我需要 dispose() 和不需要的内容。 .net 框架中有各种各样的东西;如何分离出需要我 dispose() 的东西?我看什么来告诉我我对此负责?

检查它是否是 IDisposable。

在那之后,答案继续详细讨论如何处置(),但我仍然停留在需要处置的内容上。

任何实现 IDisposable 的东西都需要被释放。

我的问题是关于何时,即需要处理什么(),而不是何时完成。我知道当我完成了一些事情时,我只是不知道当我完成它们时我要负责哪些事情。

实现 IDisposable 的东西。

我希望对于必须处理的内容有一个一般原则,而不是一长串具体项目,这对于像我这样寻求简单指导的人来说不是特别有用。

简单的准则是您应该丢弃一次性物品。

我再次明白,内存释放很重要,而且在学习原因和方法方面积累了很多经验和专业知识,但我仍然难以理解需要处理的内容。一旦我了解了我必须处理的内容(),我就可以开始努力学习如何去做。

通过调用 Dispose() 处理实现 IDisposable 的事物。

所以这仍然是一个糟糕的问题吗?

这是一个非常重复的问题。

你的耐心是一种善意。

感谢您以幽默的方式回答这个有点愚蠢的答案!

WRT 范围!= 生命周期和变量!= 对象,非常有帮助。

这些很容易混淆,而且大多数情况下,它们几乎没有什么区别。但我发现,对于那些难以理解某个概念的人来说,含糊不清和不精确通常根本无法满足他们的需求。

在 VS 中是否像在对象浏览器/智能感知中查看对象是否包含 Dispose() 一样简单?

绝大多数时候,是的。

有一些不为人知的极端案例。正如我已经提到的,TPL 团队的普遍看法是,处理 Task 对象不仅没有必要,而且可能适得其反。

还有一些类型实现了 IDisposable,但使用“显式接口实现”技巧使“Dispose”方法只能通过强制转换为 IDisposable 来访问。在大多数情况下,对象本身有 Dispose 的同义词,通常称为“关闭”或类似的东西。我不太喜欢这种模式,但有些人会使用它。

对于这些对象,using 块仍然有效。如果出于某种原因您想在不使用 using 的情况下显式处置此类对象,则 (1) 调用“Close”方法或任何它被调用的方法,或者 (2) 强制转换为 IDisposable 并处置它。

普遍的看法是:如果对象是一次性的,那么丢弃它并没有什么坏处,当你用完它时这样做是一个好习惯。

原因是:一次性对象通常代表稀缺的共享资源。例如,一个文件可能以一种模式打开,该模式拒绝其他进程在您打开该文件时访问该文件的权限。确保文件在完成后立即关闭是礼貌的做法。如果一个进程想要使用一个文件,那么另一个进程很快就会使用的可能性很大。

或者一次性可能代表图形对象之类的东西。如果一个进程中有超过一万个活动对象,操作系统将停止提供新的图形对象,所以当你完成它们时你必须让它们离开。

WRT 实现 IDisposable @Brian 的评论建议在“正常”编码中我可能不需要。那么,如果我的班级引入了一些不受管理的东西,我会这样做吗?

好问题。您应该在两种情况下实现 IDisposable。

(1) 常见场景:您正在编写一个对象,该对象长期持有另一个 IDisposable 对象,并且“内部”对象的生命周期与“外部”对象的生命周期相同。

例如:你正在实现一个记录器类,它打开一个日志文件并保持它打开直到日志关闭。现在你有一个持有一次性用品的类,所以它本身也应该是一次性用品。

我注意到,在这种情况下,“外部”对象不需要可终结。只是一次性的。如果由于某种原因从未对外部对象调用 dispose,则内部对象的终结器将负责终结。

(2) 罕见情况:您正在实现一个新类,该类向操作系统或其他外部实体询问必须积极清理的资源,并且该资源的生命周期与对象的生命周期相同坚持下去。

在这种极其罕见的情况下,您首先应该问问自己是否有任何方法可以避免这种情况。对于初学者到中级程序员来说,这是一个糟糕的情况。您确实需要了解 CLR 如何与非托管代码交互才能使这些东西变得可靠。

如果您无法避免,您最好不要尝试自己实现处置和终结逻辑,尤其是如果非托管对象由 Windows 句柄表示。大部分以句柄为代表的 OS 服务应该已经有了封装器,但如果没有,您要做的就是仔细研究 IntPtr、SafeHandle 和 HandleRef 之间的关系。 IntPtr, SafeHandle and HandleRef - Explained

如果您确实确实需要为非托管、非基于句柄的资源编写处置逻辑,并且该资源需要通过最终确定来支持处置,那么您将面临重大的工程挑战。

标准的 dispose 模式代码可能看起来很简单,但编写正确的终结逻辑却有真正的微妙之处,这种逻辑在面对错误情况时是健壮的。请记住,终结器在不同的线程上运行,并且可以在线程中止场景中与构造函数并发地在该线程上运行。编写线程安全逻辑来清理一个对象当它仍在另一个线程上构建时可能非常困难,我建议不要尝试。

有关编写终结器的挑战的更多信息,请参阅我关于该主题的系列文章:http://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/

一个你没有问但我会回答的问题:

是否存在我应该实施 IDisposable 的情况?

是的。许多人在任何时候都希望使用具有以下语义的编码模式来实现 IDisposable:

  • 改变世界
  • 在新世界做点事
  • 还原更改

因此,例如,“冒充管理员,执行一些管理任务,恢复为普通用户”。或“开始处理事件,在事件发生时做事,停止处理事件”。或者“创建一个内存错误跟踪器,做一些可能出错的事情,停止跟踪错误”。等等。你得到了一般模式。

这不适合一次性模式,但这并不能阻止人们编写不代表任何非托管资源的类,但仍然像他们那样实现 IDisposable。

这种观点使我成为少数;很多人对这种机制的滥用没有任何问题。但是,当我看到一次性用品时,我想“这门课的作者希望我礼貌,并在我准备好的时候清理干净。”但是该类的实际约定通常是“您必须在程序中的特定点处理它,如果你不这样做,那么程序逻辑的其余部分将是错误的直到你这样做”。当我看到一次性用品时,这不是我期望必须执行的合同。我希望我必须真诚地努力在我方便的时候清理资源。

【讨论】:

  • 当我第一次看到这个答案时,我期望不得不写一个评论,说如果一个问题需要这么多的文字来回答,它真的应该被关闭,因为太宽泛了。然后我读了...
  • 为了更复杂,你也可以用using调用Dispose()。但我知道你为了简单而忽略了这一点。
  • @Dorus:更好的思考方式是using 只是调用Dispose 的另一种语法。
  • 您的耐心是一种善意。 WRT 范围!= 生命周期和变量!= 对象,非常有用。正如您所料,仍然在每个人的帮助下工作。我也大声笑 b/c 有时最好的老师是重复;你明白了,我们处理一次性的。所以 2 后续行动:(1)在 VS 中是否像在对象浏览器/智能感知中查看对象是否包含 Dispose() 一样简单? (2) WRT 实现 IDisposable @Brian 的评论建议在“正常”编码中我可能不需要。那么,如果我的班级引入了一些不受管理的东西,我会这样做吗?
  • @Alan:我已经在编辑中涵盖了您的后续问题。
【解决方案2】:

垃圾收集器 (GC) 保证 托管 内存 资源 不再使用 在内存限制被释放之前到达

让我们分解一下:

托管:粗略地说,这意味着资源完全在 .NET/CLR 中。例如,由非 .NET C++ 库分配的内存由 GC 释放。

内存:GC 只提供内存使用的保证。存在许多其他资源类型,例如文件句柄。 GC 没有逻辑来确保文件句柄被方便地释放。

不再使用:这意味着所有引用该内存的变量都已结束其生命周期。正如 Eric Lippert 所解释的,生命周期 != 范围。

在达到内存限制之前:GC 监控“内存压力”并确保在需要时释放内存。它使用一堆算法来决定何时执行此操作最有效,但重要的是要注意 GC 自行决定何时释放所有资源(对您的程序而言是不确定的)。


这给我们留下了几种不适合依赖 GC 的情况:

  • 该对象引用非托管资源(包括引用一个引用非托管资源的托管对象)。
  • 对象引用了必须释放的内存以外的资源。
  • 对象引用了必须在代码中的特定点(确定性地)释放的资源。

在任何这些情况下,对象都应该实现IDisposable 来处理资源清理。

任何实现IDisposable 的实例化对象都必须通过调用Dispose 或使用using 块(负责为您调用Dispose)清理。

【讨论】:

  • 所有引用该内存的变量都已结束其生命周期。
【解决方案3】:

Dispose 模式可用于清除托管和非托管资源。如果你的类中有非托管资源,根据proper IDisposable implementation,你必须同时拥有 Dispose 和 Finalize 方法。

我怎么知道 GC 知道/不知道什么?

GC 只知道/对托管对象感兴趣。 GC 应该清除那些没有任何强引用的对象。它不知道你的逻辑。举一个简单明了的例子。

假设您有一个持续很长时间的 MainView 实例,并且您创建了另一个 LittleView 来订阅 MainView 实例中的事件。

然后关闭 LittleView,它就会消失。你知道你不再需要那个 LittleView 实例了。但是 GC 不知道您是否还需要 LittleView,因为 MainWindow 有一个活动的事件订阅。

因此,GC 不会费心从内存中清除 LittleView 实例。因此,您应该在关闭视图时取消订阅该事件。然后 GC 知道 LittleView 没有强引用并且它是可访问的。

帖子还说托管资源可能包括 非托管资源。哇。这比我最初的要深 想象的。我仍然希望轻松识别如何知道需要什么 被处置。这是一个复杂的列表吗? 条件和背景?

没错,托管对象也可以有非托管资源。所有这些托管对象都有它们的 finalize 方法来清除非托管资源。

想知道为什么除了 Dispose 之外还需要 finalize 方法?

非托管资源会造成最危险的内存泄漏,因为此类内存泄漏会一直保留在内存中,直到您重新启动 PC。所以,他们非常糟糕。

假设有托管实例 InsA 和一些非托管实例。 InsA 也以清除非托管资源的方式实现了 Dispose 方法。但是如果你不/忘记调用 Dispose 方法会发生什么?它永远不会清除非托管内存。这就是为什么 Finalization 已进入该功能的原因。因此,如果您忘记/不调用 Dispose,最终确定将保证以释放非托管资源的方式执行 Dispose。

【讨论】:

  • 直接来自 MSDN:Provides a mechanism for releasing unmanaged resources. - msdn.microsoft.com/en-us/library/…。人们如何选择使用IDisposable 取决于他们,但目的是提供一种释放非托管资源的机制。因为,让我们面对现实吧,非托管资源通常需要确定性地释放(我在考虑 I/O 锁,如果它们被非确定性地释放会多么可怕)。
  • @code4life:感谢您的反对。在同一页面中,示例代码中有一条注释“在此处释放任何其他托管对象”。 Dispose 和 finalization 非常接近,它们的目的不仅仅是清除托管资源。
  • 叹气,另一个引用来自同一个 MSDN 页面The primary use of this interface is to release unmanaged resources.。就像我说的——人们可以选择他们想要如何使用IDisposable,但让我们记住这里的设计意图。并且说 dispose 和 finalization 应该在一起是 dispose 模式的一个有趣的半陈述,恕我直言。
  • 这是一个糟糕的答案。 IDisposable 是清除非托管资源。它在一次性模式中说“托管”资源的唯一原因是如果处置对象包含对其他IDisposable 对象的引用。这是一种遍历可处置对象树以确保所有非托管资源都已处置的方法。 GC 完全能够清除托管资源。这就是它们被称为托管的原因。
  • 另外,对于LittleView 的示例,您只需删除GC 的事件处理程序即可清理对象。 除非对象实现IDisposable,否则您也不需要处理。
【解决方案4】:

如果您有一个实现IDisposable 的对象,那么您必须始终在该对象上显式调用.Dispose()。如果它没有实现IDisposable,那么显然你不会调用.Dispose(),因为你不能。

如果您正在编写自己的对象,那么关于是否实现 IDisposable 的规则很简单:如果您的对象包含对非托管对象的引用,或者如果它包含对实现 IDisposable 的对象的引用,那么它必须实现IDisposable

GC从不为您调用.Dispose()。您必须始终这样做 - 直接或通过终结器。

GC可能(最有可能,但并非总是)调用终结器,因此您可以编写终结器来调用 dispose,但请注意正确实现一次性模式,并确保你知道终结器可能永远不会运行,所以如果你的 dispose 方法做了一些非常重要的事情,最好在你失去对对象的引用之前直接调用.Dispose()

【讨论】:

  • 我很想评论一下为什么这会被否决。我认为我写的是完全有效的。
猜你喜欢
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-18
  • 2018-01-26
  • 2012-03-20
  • 2018-02-14
相关资源
最近更新 更多