【问题标题】:Destructor vs IDisposable?析构函数与 IDisposable?
【发布时间】:2014-04-16 17:54:23
【问题描述】:

我读过关于在 C# 中处理对象/IDisposable 接口和析构函数的文章,但在我看来,它们似乎在做同样的事情?

两者有什么区别?为什么我要使用其中一个?事实上,在这个例子中(下面的链接),这段代码同时使用了 IDisposable 接口和析构函数:

http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

评论说析构函数是如果不使用终结代码,但我如何决定何时使用一个而不是另一个?

【问题讨论】:

标签: c# .net


【解决方案1】:

我写了一篇相当深入的文章,应该有助于解释终结器、IDisposable 以及何时应该使用其中一个:http://gregbee.ch/blog/implementing-and-using-the-idisposable-interface

下面引用了最相关的部分:

当您使用非托管资源时 例如句柄和数据库 连接,您应该确保 他们被持有的最低金额 时间,利用原理 晚获取早释放。在 C++ 中 释放资源通常是 在析构函数中完成,即 确定性地在该点运行 删除对象的位置。互联网 然而,运行时使用垃圾 收集器(GC)清理和回收 非对象使用的内存 更远可达;因为这运行在 周期性基础 它意味着该点 你的对象被清理的地方是 不确定的。的后果 这是析构函数不存在 对于托管对象,因为没有 运行它们的确定性位置。

C# 没有析构函数 由以下方式实现的终结器 覆盖定义的 Finalize 方法 在基 Object 类上(虽然 C# 有点混乱地使用 C++ 析构函数语法〜对象)。 如果一个对象覆盖了 Finalize 方法然后而不是 GC 出时收集 作用域,GC 把它放在一个终结器上 队列。在下一个 GC 循环中,所有 队列上的终结器运行(在 当前单线程 实现)和内存来自 最终的对象被回收。它是 很明显,为什么你不这样做 想要在终结器中进行清理:它 需要两个 GC 周期来收集 对象而不是一个,并且有一个 所有终结器都在的单线程 在所有其他线程运行时运行 暂停,所以会很痛 性能。

所以如果你没有析构函数,并且 你不想把清理工作留给 终结器,那么唯一的选择是 手动、确定地、清洁 对象。输入 IDisposable 提供标准的接口 支持此功能和 定义了一个方法 Dispose, 你把清理逻辑放在哪里 物体。在 finally 内使用时 块,该接口提供 等效功能 析构函数。最后的原因 代码中的块主要是为了支持 IDisposable 接口;这就是为什么 C++ 简单地使用 try/except,因为有 不需要 finally 块 析构函数。

【讨论】:

  • 链接显然已失效,您还有其他帖子链接吗?
  • 至少现在您仍然可以从 Google 的缓存中检索它:goo.gl/1dTmL
  • 略显不足:C# 中用波浪号后跟类名表示的句法元素在 C# 中被恰当地称为析构函数,因为这是编写 C# 规范的人使用的术语。 C# 析构函数的行为与 C++ 析构函数的行为无关,而且这个名字相当愚蠢,因为 C# 析构函数实际上并没有销毁一个对象,而是给出了一个 否则会被销毁的对象 a缓刑,这样它就可以把它的事情整理好。
  • @supercat - 微软过去实际上已经使用这两个词来描述 C# 上下文中的此功能。尽管官方文档倾向于使用“析构函数”一词,但我更喜欢使用“终结器”一词以避免混淆或暗示错误的语义。许多设计 .NET 框架的人都同意这种立场:bluebytesoftware.com/blog/…
  • @GregBeech:C#肯定强调了对象生命周期的错误方面,并且以一种假装可移植但实际上并非如此的方式这样做。此外,从 CLR 的角度来看,GC 运行终结器并且不知道析构函数是什么。我认为我们在那里达成了激烈的协议。我的观点是,在讨论 C# 如何包装终结器的怪癖时,有必要使用术语“析构函数”,无论术语选择多么不幸。
【解决方案2】:

短版

终结器为您提供了一个机会来处理非托管资源,以防您的对象的用户忘记调用IDisposable.Dispose

如果您的对象实现了IDisposable,您的对象的用户必须调用.Dispose。你没有清理用户的烂摊子;但这是一件好事。


我的most popular answer on Stackoverflow 从一开始就带您了解为什么要使用 IDisposable、它应该做什么、终结器可以做什么、不应该做什么。

这个答案让人脸红

已经被用来形容了:P

【讨论】:

  • 你可以做得更好! (对不起,不得不。同样,无法抗拒对该答案的支持,尽管最后的“这是基本课程应该是什么样子”可能会对其他人有所帮助)在旁注中,这是一个社区 wiki 答案,所以你不会得到积分,对吧?
  • @JoeBrockhaus 我想我不知道。如果您继续编辑答案以使其变得更好,就会发生这种情况。
【解决方案3】:

在托管编程语言中使用析构函数 (~Object()) 是最愚蠢的想法。 对于像 C、C++ 这样的非托管语言来说,使用 RAII 惯用语具有析构函数是完全合理的,但对于像 Java、C# 这样的托管语言来说,这太荒谬了。

Java Collection Framework 的前项目负责人 Joshua Bloch 曾指出,Java 中的 finalize() 方法(相当于 C# 的 C++ 类析构函数)的想法是有史以来最大的错误。与 C# 一样,Java 中的 finallize() 给“new”带来了开销,因为它必须在分配期间添加到 finallizer 队列中。此外,垃圾收集器必须在队列中弹出并运行 finallize(),因此 gc 期间的开销增加了一倍。

C# 具有许多增强功能,例如“using(IDisposable) {}”,它不仅允许 IDisposable 变量被限制在“using”块的范围内,而且还保证了它的清理。 我的问题是,为什么 C# 遵循 Java 的相同轨迹导致了大错误。 如果 dotnet 的发展是在 2003~2005 年左右之后开始的,当时很多 Java 架构师发现了 finallize() 的谬误,那么这个错误就可以避免了。

许多关于一种语言的好想法经常被转移到其他语言,例如 C# 中的“IDisposable/using combo”,它在“try(object-to-dispose) {}”语句中被转移到 Java 1.7。但是,语言架构师在从一个到另一个转换过程中没有发现伪装成好主意的坏主意,这太糟糕了。

如果您需要手动清理数据库连接等非托管资源,我的建议是永远不要使用 ~Destructor() 并坚持使用 IDisposable/使用组合。

【讨论】:

  • 嗨,David,IDisposable 是管理分配的内存所必需的,而不是像 WinForms 中使用的窗口那样由框架分配。在语言完成之前是必需的(没有外部依赖项)。
  • 理论上我同意;最好不需要它们。但是,由于您不能指望调用者在实践中调用 dispose,因此有时需要额外的陷门来确保最终确定。一次性模式 (goo.gl/YoGsr) 专门用于仅在客户端忽略调用 dispose 时才调用 finalize。秉承“因为理论”!参数让我们对任何 IDisposable 都强制使用 using 语句,这不是一个站得住脚的解决方案——甚至比 java 中的检查/未检查异常问题更糟糕。
  • 嗨@dcw,我承认同时拥有 Dispose() 和 finallize() 使它成为管理资源的防弹解决方案,在垃圾收集之前必须进行清理。但是我几乎看不到 finallize() 真的来救援并清理垃圾收集暂停或 DB 池清空错误等由于未调用 Dispose() 的情况。我在 Windows 2003/asp.net 和 IBM AIX/JDK1.5/spring 中都看到了这种情况。大多数情况下,webapp 会无限期暂停,我只是认为 gabage 收集线程已死,所以我只需重新启动特定的 COM+ 或 Java Web 容器。
猜你喜欢
  • 2018-10-30
  • 2010-09-25
  • 2012-09-02
  • 2014-02-11
  • 1970-01-01
  • 1970-01-01
  • 2018-08-29
  • 2010-09-19
  • 2012-11-12
相关资源
最近更新 更多