我最近与同事讨论了Dispose
的价值以及实施IDisposable
的类型。
我认为实施IDisposable
对于应该尽快清理的类型是有价值的,即使没有非托管资源可以清理。
我的同事有不同的想法;如果您没有任何非托管资源,则不需要实现IDisposable
,因为您的类型最终将被垃圾收集。
我的论点是,如果您想要尽快关闭ADO.NET连接,那么实施IDisposable
和using new MyThingWithAConnection()
是有意义的。我的同事回答说,在幕后,ADO.NET连接是非托管资源。我对他回复的回复是一切最终都是一种非托管资源。
我知道recommended disposable pattern如果Dispose
被调用,免费提供托管和非托管资源,但只有免费的非托管资源(如果通过终结器/析构函数调用) (前一段时间关于如何alert consumers of improper use of your IDisposable types)
所以,我的问题是,如果你有一个不包含非托管资源的类型,是否值得实现IDisposable
?
答案 0 :(得分:35)
IDisposable
有不同的有效用途。一个简单的例子就是保存一个打开的文件,只要你不再需要它就需要在某个时刻关闭。当然,您可以提供方法Close
,但在Dispose
中使用该方法并使用using (var f = new MyFile(path)) { /*process it*/ }
这样的模式会更加例外。
一个更受欢迎的示例是保留其他一些IDisposable
资源,这通常意味着您需要提供自己的Dispose
以便处理它们。
一般情况下,只要您想要确定性地销毁任何内容,就需要实施IDisposable
。
我和你的意见之间的区别在于,一旦某些资源需要确定性销毁/释放,我就会实施IDisposable
,而不是必须尽快 。在这种情况下,依赖垃圾收集不是一种选择(与你同事的说法相反),因为它发生在不可预测的时刻,实际上可能根本不会发生!
任何资源在封面下都不受管理这一事实并不意味着什么:开发人员应该考虑“何时以及如何处理此对象”,而不是“它如何在封面下工作” ”。无论如何,底层实现可能随时间而变化。
事实上,C#和C ++之间的主要区别之一是缺少默认的确定性破坏。 IDisposable
缩小了差距:您可以订购确定性销毁(虽然您无法确保客户端正在调用它;但在C ++中,您无法确定客户端是否在对象上调用delete
)。
小补充:确定性释放资源并尽快释放之间的差异实际上有什么区别?实际上,那些是不同的(虽然不是完全正交的)概念。
如果要确定性地释放资源,这意味着客户端代码应该可以说“现在,我希望释放此资源”。这可能实际上不是可以释放资源的最早可能的时刻:持有资源的对象可能已从资源获得所需的一切,因此可能已经释放资源。另一方面,即使在对象的Dispose
经过之后,对象也可能选择保留(通常是非托管的)资源,仅在终结器中清理它(如果保留资源太长时间没有任何问题) )。
因此,为了尽快释放资源 ,严格来说,Dispose
不是必需的:对象可以在它意识到资源不是时立即释放资源需要更多。 Dispose
但是作为一个有用的提示,不再需要对象本身,因此,如果适当的话,也可以在那时释放资源 。
还有一个必要的补充:不仅需要确定性解除分配的非托管资源!这似乎是这个问题答案中意见分歧的关键点之一。人们可以拥有纯粹富有想象力的结构,可能需要确定性地释放。
示例包括:访问某些共享结构的权利(想想RW-lock),一个巨大的内存块(想象一下你手动管理一些程序的内存),使用其他程序的许可证(想象一下)你不能同时运行某些程序的 X 副本),等等。这里要释放的对象不是非托管资源,而是对来做/使用某些东西,这是程序逻辑的纯粹内部构造。
小补充:这里有一小部分[ab]使用IDisposable
:http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable的简洁例子。
答案 1 :(得分:17)
我认为在责任方面考虑IDisposable
是最有帮助的。一个对象应该实现IDisposable
,如果它知道在不再需要它和宇宙结束之间需要完成的事情(并且最好是尽快),并且如果它是两者的唯一对象这样做的信息和动力。例如,打开文件的对象将有责任查看文件是否已关闭。如果对象只是在不关闭文件的情况下消失,则文件可能无法在任何合理的时间范围内关闭。
重要的是要注意,即使只与100%托管对象交互的对象也可以执行需要清理的事情(并且应该使用IDisposable
)。例如,附加到集合的“已修改”事件的IEnumerator
将需要在不再需要时自行分离。否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器就永远不会被垃圾收集。如果集合被枚举了一百万次,那么一百万个枚举器将附加到它的事件处理程序。
请注意,有时可以使用终结器进行清理,无论出于何种原因,在没有首先调用Dispose
的情况下放弃对象。有时候效果很好;有些东西很糟糕。例如,即使Microsoft.VisualBasic.Collection
使用终结器将枚举器从“已修改”事件中分离出来,尝试枚举此类对象数千次而没有介入Dispose
或垃圾回收将导致它变得非常慢 - 比正确使用Dispose
时所产生的性能慢几个数量级。
答案 2 :(得分:9)
所以,我的问题是,如果你有一个不包含的类型 非托管资源,是否值得实施IDisposable?
当有人在对象上放置IDisposable接口时,这告诉我创建者打算在该方法中执行某些操作,或者在将来他们可能打算这样做。我总是在这个例子中称为dispose只是为了确定。即使它现在没有做任何事情,它可能在未来,并且因为更新了对象而导致内存泄漏很糟糕,并且在第一次编写代码时没有调用Dispose。
事实上,这是一个判断。你不想过度实现它,因为在那一点上为什么还要烦扰垃圾收集器。为什么不手动处理每个对象。如果您有可能需要处置非托管资源,那么这可能不是一个坏主意。这完全取决于,如果使用您的对象的唯一人员是您团队中的人员,您可以随后跟进他们并说:“嘿,现在需要使用非托管资源。我们必须仔细检查代码并确保我们整理了一下。“如果您要将其发布给其他组织以使用其他组织。没有简单的方法可以告诉每个可能已经实现该对象的人,“嘿,你需要确保现在已经处理掉了。”让我告诉你,有些事情让人们感到羞耻,而不是升级第三方程序集,以发现他们是那些更改代码并使你的应用程序逃避内存问题的人。
我的同事回答说,在封面下,ADO.NET连接是一个 管理资源。我对他的答复的答复是最终的一切 是一种非托管资源。
他是对的,现在它是一个托管资源。他们会改变吗?谁知道,但称之为没有伤害。我没有尝试猜测ADO.NET团队做了什么,所以如果他们把它放入并且什么都不做,那就没问题。我仍然会称之为,因为一行代码不会影响我的工作效率。
您还遇到了另一种情况。假设您从方法返回ADO.NET连接。您不知道ADO连接是基础对象还是派生类型。您不知道是否突然需要该IDisposable实现。无论如何,我总是称之为,因为生产服务器上的内存泄漏在每4小时崩溃时就会很糟糕。
答案 3 :(得分:6)
虽然已经有了很好的答案,但我只是想做一些明确的事情。
实施IDisposable
有三种情况:
IntPrt
或其他形式的句柄,该调用必须由不同的P / Invoke调用释放IDisposable
个对象,需要对其处置负责using
块的便利性。虽然我可能有点偏颇,但您应该阅读(并向同事展示)the StackOverflow Wiki on IDisposable
。
答案 4 :(得分:5)
不,对于非托管资源,不 仅。
它建议像框架调用的基本清理内置机制,它使您可以清理所需的任何资源,但最适合的是自然的非托管资源管理。
答案 5 :(得分:5)
请注意,非托管资源可能包含标准CLR对象,例如保存在某些静态字段中,所有对象都以安全模式运行,根本没有非托管导入。
没有简单的方法可以判断实现IDiposable
的给定类是否确实需要清理某些内容。我的经验法则是总是在我不太了解的对象上调用Dispose
,就像第三方库一样。
答案 6 :(得分:5)
Dispose
应该用于有限生命期的任何资源。终结器应该用于任何非托管资源。任何非托管资源都应该具有有限的生命周期,但是有大量的托管资源(如锁)也具有有限的生命周期。
答案 7 :(得分:3)
如果你聚合IDisposable
,那么你应该实现界面,以便及时清理这些成员。在你引用的ADO.Net连接示例中,如何调用myConn.Dispose()
?
我认为在这种情况下,所有内容都是非托管资源是不正确的。我也不同意你的同事。
答案 8 :(得分:3)
你是对的。托管数据库连接,文件,注册表项,套接字等都保留在非托管对象上。这就是他们实施IDisposable
的原因。如果您的类型拥有一次性对象,则应实现IDisposable
并将其置于Dispose
方法中。否则,他们可能会一直活着,直到垃圾收集,导致锁定文件和其他意外行为。
答案 9 :(得分:3)
一切最终都是一种非托管资源。
不正确。 CLR对象使用的内存以外的所有内容,只由框架管理(分配和释放)。
在未保留任何非托管资源(直接或间接通过相关对象)的对象上实施IDisposable
并调用Dispose
毫无意义。它确实不释放该对象确定性,因为无法直接释放对象的CLR内存,因为它总是只有{{1这样做。对象是引用类型,因为值类型在直接在方法级别使用时,由堆栈操作分配/释放。
现在,每个人都声称他们的答案是正确的。让我证明我的。根据{{3}}:
Object.Finalize Method允许对象在垃圾收集回收之前尝试释放资源并执行其他清理操作。
换句话说,在调用GC
之后释放对象的CLR内存。 [注意:如果需要,可以明确地跳过此调用]
这是一个没有非托管资源的一次性课程:
Object.Finalize()
请注意,documentation隐式调用继承链中的每个internal class Class1 : IDisposable
{
public Class1()
{
Console.WriteLine("Construct");
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
~Class1()
{
Console.WriteLine("Destruct");
}
}
至Finalize
以下是控制台应用的Object.Finalize()
方法:
Main
如果调用static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Class1 obj = new Class1();
obj.Dispose();
}
Console.ReadKey();
}
是一种以确定方式释放托管对象的方法,那么每个“Dispose”会立即跟随“Destruct”,对吗?看看自己会发生什么。从命令行窗口运行此应用程序是最有趣的。
注意:有一种方法可以强制Dispose
收集当前应用程序域中待定位的所有对象,但对于单个特定对象则不会。不过,您无需调用GC
在终结队列中拥有对象。强烈建议不要强行收集,因为这可能会影响整体应用程序的性能。
修改强>
有一个例外 - 国家管理。如果您的对象恰好管理外部状态,Dispose
可以处理状态更改。即使状态不是非托管对象,由于Dispose
的特殊处理,使用它也非常方便。示例可以是安全上下文或模拟上下文。
IDisposable
这不是最好的例子,因为using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate()))
{
// do something as SomeUser
}
// back to your user
在内部使用了系统句柄,但你得到了图片。
底线是,在实施WindowsImpersonationContext
时,您需要(或计划拥有)IDisposable
方法中有意义的事情。否则,这只是浪费时间。 Dispose
不会更改GC管理对象的方式。
答案 10 :(得分:1)
如果它引用了非托管资源,或者它包含对实现IDisposable的对象的引用,那么你的Type应该实现IDisposable。
答案 11 :(得分:1)
在我的一个项目中,我有一个带有托管线程的类,我们将它们称为线程A,线程B和IDisposable对象,我们将其称为C.
用于处理退出时的C. B曾经使用C来保存例外。
我的班级必须实施IDisposable和descrtuctor,以确保以正确的顺序处理事情。 是的,GC可以清理我的物品,但我的经验是有一个竞争条件,除非我管理我的班级清理。
答案 12 :(得分:1)
简答:绝对不是。如果您的类型具有托管或非托管成员,则应实现IDisposable。
现在详情: 我已回答了这个问题,并在StackOverflow上提供了有关内存管理内容和GC问题的更多细节。这里只是几个:
关于IDisposable实施的最佳实践,请参阅我的博客文章:
<强> How do you properly implement the IDisposable pattern? 强>
答案 13 :(得分:0)
如果对象拥有任何非托管对象或任何托管的一次性对象
,请实施IDisposable
如果对象使用非托管资源,则应实现IDisposable
。拥有一次性对象的对象应实现IDisposable
以确保释放基础非托管资源。如果遵循规则/约定,那么可以合理地得出结论:不处理托管的一次性对象等于不释放非托管资源。
答案 14 :(得分:0)
根本不需要资源(托管或非托管)。通常,IDisposable
只是方便的方式来消除精简版try {..} finally {..}
,只需比较:
Cursor savedCursor = Cursor.Current;
try {
Cursor.Current = Cursors.WaitCursor;
SomeLongOperation();
}
finally {
Cursor.Current = savedCursor;
}
带
using (new WaitCursor()) {
SomeLongOperation();
}
其中WaitCursor
IDisposable
适合using
:
public sealed class WaitCursor: IDisposable {
private Cursor m_Saved;
public Boolean Disposed {
get;
private set;
}
public WaitCursor() {
Cursor m_Saved = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
}
public void Dispose() {
if (!Disposed) {
Disposed = true;
Cursor.Current = m_Saved;
}
}
}
您可以轻松合并这些类:
using (new WaitCursor()) {
using (new RegisterServerLongOperation("My Long DB Operation")) {
SomeLongRdbmsOperation();
}
SomeLongOperation();
}