3

我刚刚开始使用 .NET 框架。今天,我学习了IDisposable接口和dispose()方法。我被教导了一些关于它的事情:

dispose()应包含与对象对应的清理代码(如关闭任何对象占用的任何资源 - 文件或数据库连接等)

我还被告知,如果我们不在dispose()方法中执行此操作,则可以在析构函数中执行相同的操作,但这并不能确保立即执行,我们只能任由 GC 摆布。

如果我们根本不提供任何清理代码,GC 将强制终止与我们的对象持有的资源的所有连接。因此,我们应该自己处理清理代码。

但我很好奇为什么 CLR 不自己处理这个问题?它负责内存管理,负责垃圾收集。因此,它应该非常清楚哪个对象持有哪个资源以及该对象何时消亡。那么,它也应该能够重新分配这些资源?

我问了几个人。我得到的答案是因为我们需要优雅地关闭它,而 GC 会强制关闭它。真的是这个原因吗?

4

8 回答 8

3

在 .NET中,GC 知道的远不止托管代码。涉及大量非托管代码:所有文件句柄、数据库连接、网络套接字……所有这些都是普通的非托管 Win32 代码。您甚至无法相信,在您从漂亮的 C# 应用程序调用的几乎每一个 BCL 函数中,您都会遇到大量用 C++ 编写的非托管函数(愿上帝禁止 VB6),并深入操作系统的内部本身。所有这些函数都在分配非托管内存、句柄……托管世界不知道那里发生了什么。

例如,每次打开文件 ( FileStream) 时,您基本上都在调用(当然是在幕后)CreateFile非托管 Win32 函数。该函数直接从文件系统分配一个非托管文件句柄。.NET 和 GC 严格无法跟踪此非托管代码及其所做的一切。这就是这些类实现 IDisposable 接口的原因。这样您就可以始终将它们的实例包装在using 语句中,并确保始终调用 Dispose 方法,即使在发生异常时也是如此,并且尽可能快。Dispose 方法将负责调用另一个非托管函数来清理它造成的混乱。

所以基本上你可以考虑 IDisposable 接口的方式如下:

当我们拥有用完全托管的语言编写的操作系统(例如来自 Microsoft Research 的Midori)时,我们可能不再需要 IDisposable,因为 GC 将能够完全替换它,因为它知道发生的所有事情在这个系统内。

于 2012-07-27T15:07:05.080 回答
2

IDisposable的重点Dispose()是你应该清理非托管内存。那是 .NET 没有分配的内存,它来自外部来源,因此 GC 无法知道它。所以它不能自动为你清理它。本质上,这正是托管内存和非托管内存之间的区别;-)

通常,您应该实现Dispose()以清理您的类使用的任何非托管资源并实现终结器来调用Dispose()。不过,终结器只是一种保护措施。如果调用者忘记正确处理您的类,它将确保最终清理这些资源。

于 2012-07-27T15:07:28.563 回答
1

IDisposable界面为您提供了一种清理非托管资源的方法。CLR 只为您管理托管资源。

换句话说,CLR 只知道如何清理它管理的东西。如果您打开与系统其余部分的连接(如打开文件、数据库连接等),这些都是您的责任,您需要告诉 CLR 您希望它如何为您清理这些连接。

于 2012-07-27T15:06:36.603 回答
1

IDisposable接口只是一个约定,允许您确定地处置托管和非托管资源。它本身并不能取代垃圾收集或做任何涉及垃圾收集器本身的事情。

非托管资源更明显,因为除非这些资源被处理(在终结器中或通过确定性处置),否则它们将一直作为内存泄漏,直到进程结束。使用托管内存,如果您不确定地处置项目,GC 将不确定地收集它们(假设最终有资格收集),因为它们是托管的(这也是处置模式不包括托管项目的原因在终结器路线中)。

IDisposable它本身不做任何事情,它只是using人们在处理使用消耗性资源、非托管内存、外部项目等的项目时期望找到的公认接口(并且在代码中支持关键字)。

CLR 不可能知道外部项目何时完成。这完全取决于您的应用程序的流程。如果您碰巧也不知道何时释放对象,那么终结器语法很有用。如果您在自定义类上实现终结器,垃圾收集过程将在最终收集之前运行此终结器。这是你整理自己的最后机会。

于 2012-07-27T15:07:44.367 回答
1

它只能处理 .NET 对象的内存管理。任何需要使用非托管资源的代码(例如,因为它与 C++ 库交互)都在垃圾收集器的管辖范围之外。需要告知所有代码何时以老式方式释放其资源。

于 2012-07-27T15:07:52.270 回答
1

.Net 框架(和 GC)无法知道如何释放非托管资源。它所能做的就是破坏托管代码对资源的引用。在与数据库服务器的连接上实际调用 .Close() 会好得多(从而告诉它连接应该返回到可用连接的轮询中),而不是仅仅破坏引用并让它自己超时经过一定的秒数。

因此,在引用非托管资源时,请尽可能使用 IDisposable 接口!

于 2012-07-27T15:10:54.517 回答
1

IDisposable当您不希望 GC 处理该特定工件时使用。最常见的示例是连接或文件句柄。您不希望在释放文件之前等待 GC 运行,或者关闭与数据库的连接,因为您不知道什么时候会发生。

大多数人IDisposable与非托管资源相关联,这在很大程度上是准确的,但没有记住终结器是处理这些资源的正确 .NET 方式。IDisposable如果这对您的程序很重要,则提供一种确定性处理的方法。

于 2012-07-27T15:11:05.003 回答
0

我们使用 Dispose 是为了将非托管资源处理为文件访问或连接数据库,因为 GC 没有关于此非托管资源的信息。

您也可以使用 Finalize,但它不是高性能的,因为您将资源保存在 finalization 结构中,并且 GC 通过这个 finalization 结构在 dispose 周期结束时通过,并且它不是高性能的

于 2012-07-27T15:10:37.423 回答