处理 .NET IDisposable 对象
我使用 C# 工作,对于使用 using
块来声明实现 IDisposable
的对象,我一直相当宽松,而您显然总是应该这样做。 然而,我没有找到一种简单的方法来知道我何时犯了错误。 Visual Studio 似乎没有以任何方式表明这一点(我只是错过了什么吗?)。 我是否应该在每次声明任何内容时检查帮助,并逐渐建立一个百科全书式的记忆,哪些对象是一次性的,哪些对象不是一次性的? 似乎没有必要、痛苦且容易出错。
你如何处理这个问题?
编辑:
查看相关问题侧边栏,我发现另一个问题,它清楚地表明 Dispose()
无论如何都应该由对象的终结器调用。 因此,即使您从未自己调用它,它最终也会发生,这意味着如果您不使用 using
,您就不会出现内存泄漏(这就是我想我一直以来真正担心的问题) )。 唯一需要注意的是,垃圾收集器不知道对象作为非托管内容保留了多少额外内存,因此它无法准确了解收集对象将释放多少内存。 这将导致垃圾收集器的性能不如平时理想。
简而言之,如果我错过了 using
,那也不是世界末日。 我只是希望某些东西至少会产生一个警告。
(题外话:为什么没有特殊的降价链接到另一个问题?)
编辑:
好吧,好吧,别吵了。 这是超级骗人的全火戏剧花栗鼠级重要调用Dispose()
,否则我们都会死。
现在。 既然如此,为什么这么容易——见鬼,为什么它甚至被允许——做错事呢? 你必须不遗余力地把事情做好。 像其他事情一样这样做会导致世界末日(显然)。 封装就这么多吧?
[厌恶地走开]
I work in C#, and I've been pretty lax about using using
blocks to declare objects that implement IDisposable
, which you're apparently always supposed to do. However, I don't see an easy way of knowing when I'm slipping up. Visual Studio doesn't seem to indicate this in any way (am I just missing something?). Am I just supposed to check help every time I declare anything, and gradually build up an encyclopedic memory for which objects are and which are not disposable? Seems unnecessary, painful, and error-prone.
How do you handle this?
EDIT:
Looking at the related questions sidebar, I found another question which made it clear that Dispose()
is supposed to be called by the object's finalizer anyway. So even if you never call it yourself, it should eventually happen, meaning you won't have a memory leak if you don't use using
(which is what I suppose I was really worried about all along). The only caveat is that the garbage collector doesn't know how much extra memory is being held by the object as unmanaged stuff, so it won't have an accurate idea how much memory will be freed by collecting the object. This will result in less-ideal-than-usual performance by the garbage collector.
In short, it's not the end of the world if I miss a using
. I just wish something would generate at least a warning for it.
(Off-topic: why is there no special markdown for linking to another question?)
EDIT:
Ok, fine, stop clamoring. It's super duper all-fired dramatic-chipmunk-level important to call Dispose()
or we'll all die.
Now. Given that, why is it so easy — hell, why is it even allowed — to do it wrong? You have to go out of your way to do it right. Doing it like everything else results in armageddon (apparently). So much for encapsulation, huh?
[Stalks off, disgusted]
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
如果一个对象实现了 IDisposable 接口,那么它是有原因的,你应该调用它,并且它不应该被视为可选的。 最简单的方法是使用
using
块。Dispose()
不仅仅旨在由对象的终结器调用,事实上,许多对象将实现Dispose()
但没有终结器(这是完全有效的)。处置模式背后的整个想法是,您提供了一种某种确定性的方式来释放对象(或其继承链中的任何对象)维护的非托管资源。 如果不正确调用
Dispose()
,您绝对可能遇到内存泄漏(或任何其他问题)。Dispose()
方法与析构函数没有任何关系。 .NET 中最接近析构函数的是终结器。 using 语句不会执行任何释放操作...事实上,调用 Dispose() 不会在托管堆上执行任何释放操作; 它仅释放已分配的非托管资源。 在 GC 运行并收集分配给该对象图的内存空间之前,托管资源并未真正释放。确定类是否实现
IDisposable
的最佳方法是:Dispose()
或Close()
方法)IDisposable
,你会得到一个编译器错误)Open()
,则可能应该调用相应的Close()
)using
语句中。 如果它没有实现 IDisposable,编译器将生成错误。将处置模式视为与作用域生命周期管理有关的全部内容。 你希望尽可能最后获取资源,尽可能快地使用,并尽快释放。 using 语句通过确保即使出现异常也会调用
Dispose()
来帮助实现此目的。If an object implements the
IDisposable
interface, then it is for a reason and you are meant to call it and it shouldn't be viewed as optional. The easiest way to do that is to use ausing
block.Dispose()
is not intended to only be called by an object's finalizer and, in fact, many objects will implementDispose()
but no finalizer (which is perfectly valid).The whole idea behind the dispose pattern is that you are providing a somewhat deterministic way to release the unmanaged resources maintained by the object (or any object in it's inheritance chain). By not calling
Dispose()
properly you absolutely can run in to a memory leak (or any number of other issues).The
Dispose()
method is not in any way related to a destructor. The closest you get to a destructor in .NET is a finalizer. The using statement doesn't do any deallocation...in fact callingDispose()
doesn't do any deallocation on the managed heap; it only releases unmanaged resources that had been allocated. The managed resources aren't truely deallocated until the GC runs and collects the memory space allocated to that object graph.The best ways to determine if a class implements
IDisposable
are:Dispose()
or aClose()
method)IDisposable
you get a compiler error)Open()
, there is probably a correspondingClose()
that should be called)using
statement. If it doesn't implement IDisposable, the compiler will generate an error.Think of the dispose pattern as being all about scope lifetime management. You want to acquire the resource as last as possible, use as quickly as possibly, and release as soon as possible. The using statement helps to do this by ensuring that a call to
Dispose()
will be made even if there are exceptions.这就是为什么(恕我直言)C++ 的 RAII 优于 .NET 的
using
语句。很多人说 IDisposable 仅适用于非托管资源,这仅取决于您如何定义“资源”。 您可以拥有一个实现 IDisposable 的读/写锁,然后“资源”就是对代码块的概念访问。 您可以有一个对象,在构造函数中将光标更改为沙漏形,并返回到之前在 IDispose 中保存的值,然后“资源”就是更改后的光标。 我想说,当您希望在离开范围时执行确定性操作时,无论范围如何离开,您都可以使用 IDisposable,但我必须承认,它远不如说“它用于管理非托管资源管理”那么吸引人。
另请参阅有关为什么 .NET 中没有 RAII 的问题。
This is why (IMHO) C++'s RAII is superior to .NET's
using
statement.A lot of people said that
IDisposable
is only for un-managed resources, this is only true depending on how you define "resource". You can have a Read/Write lock implementingIDisposable
and then the "resource" is the conceptual access to the code block. You can have an object that changes the cursor to hour-glass in the constructor and back to the previously saved value inIDispose
and then the "resource" is the changed cursor. I would say that you use IDisposable when you want deterministic action to take place when leaving the scope no matter how the scope is left, but I have to admit that it's far less catchy than saying "it's for managing un-managed resource management".See also the question about why there's no RAII in .NET.
这里的问题是,您不能总是通过将 IDisposable 包装在
using
块中来处理它。 有时您需要让物体停留更长时间。 在这种情况下,您必须自己显式调用其Dispose
方法。一个很好的例子是 类使用私有 EventWaitHandle (或 AutoResetEvent) 来在两个线程之间进行通信,并且您希望在线程完成后释放 WaitHandle。
因此,它并不像某些工具只是检查您是否只在
using
块中创建 IDisposable 对象那么简单。The problem here is that you can't always deal with an IDisposable by just wrapping it up in a
using
block. Sometimes you need the object to hang around for a bit longer. In which case you will have to call itsDispose
method explicitly yourself.A good example of this is where a class uses a private EventWaitHandle (or an AutoResetEvent) to communicate between two threads and you want to Dispose of the WaitHandle once the thread is finished.
So it isn't as simple as some tool just checking that you only create IDisposable objects within a
using
block.@Atario,不仅接受的答案是错误的,您自己的编辑也是错误的。 想象一下以下情况(实际发生在 Visual Studio 2005 的一个 CTP 中):
为了绘制图形,您创建笔而不处理它们。 笔不需要大量内存,但它们在内部使用 GDI+ 句柄。 如果不处理笔,GDI+ 手柄将不会被释放。 如果您的应用程序不是内存密集型的,则可能会花费相当长的时间而不会调用 GC。 但是,可用的 GDI+ 句柄数量受到限制,很快,当您尝试创建新笔时,操作就会失败。
事实上,在Visual Studio 2005 CTP中,如果您使用该应用程序足够长的时间,所有字体都会突然切换为“System”。
这正是依赖 GC 进行处置不够的原因。 内存使用量不一定与您获取(且不释放)的非托管资源数量相关。 因此,这些资源可能早在GC被调用之前就被耗尽了。
此外,当然,这些资源可能具有的副作用(例如访问锁)会阻止其他应用程序正常工作。
@Atario, not only the accepted answer is wrong, your own edit is as well. Imagine the following situation (that actually occurred in one CTP of Visual Studio 2005):
For drawing graphics, you create pens without disposing them. Pens don't require a lot of memory but they use a GDI+ handle internally. If you don't dispose the pen, the GDI+ handle will not be released. If your application isn't memory intensive, quite some time can pass without the GC being called. However, the number of available GDI+ handles is restricted and soon enough, when you try to create a new pen, the operation will fail.
In fact, in Visual Studio 2005 CTP, if you used the application long enough, all fonts would suddenly switch to “System”.
This is precisely why it's not enough to rely on the GC for disposing. The memory usage doesn't necessarily corelate with the number of unmanaged resources that you acquire (and don't release). Therefore, these resoures may be exhausted long before the GC is called.
Additionally, there's of course the whole aspects of side-effects that these resources may have (such as access locks) that prevent other applications from working properly.
我实际上没有任何内容可以添加到Using块的一般用法中,只是想向规则添加一个例外:
任何实现IDisposable的对象显然不应在其Dispose()方法期间抛出异常。 在 WCF 之前(可能还有其他),这种方法工作得很好,现在 WCF 通道在 Dispose() 期间可能会引发异常。 如果在 using 块中使用它时发生这种情况,则会导致问题,并需要实施异常处理。 这显然需要更多关于内部工作原理的知识,这就是为什么微软现在建议不要在使用块中使用WCF通道(抱歉找不到链接,但谷歌中有很多其他结果),即使它实现了IDisposable..只是为了让事情变得更多复杂的!
I don't really have anything to add to the general use of Using blocks but just wanted to add an exception to the rule:
Any object that implements IDisposable apparently should not throw an exception during its Dispose() method. This worked perfectly until WCF (there may be others), and it's now possible that an exception is thrown by a WCF channel during Dispose(). If this happens when it's used in a Using block, this causes issues, and requires the implementation of exception handling. This obviously requires more knowledge of the inner workings, which is why Microsoft now recommends not using WCF channels in Using blocks (sorry could not find link, but plenty other results in Google), even though it implements IDisposable.. Just to make things more complicated!
不幸的是,FxCop 或 StyleCop 似乎都没有对此发出警告。 正如其他评论者所提到的,确保调用 dispose 通常非常重要。 如果我不确定,我总是检查对象浏览器 (Ctrl+Alt+J) 以查看继承树。
Unfortunately, neither FxCop or StyleCop seem to warn on this. As other commenters have mentioned, it is usually quite important to make sure to call dispose. If I'm not sure, I always check the Object Browser (Ctrl+Alt+J) to look at the inheritance tree.
我主要在这种情况下使用 using 块:
我正在使用一些外部对象(在我的情况下通常是 IDisposable 包装的 COM 对象)。 对象本身的状态可能会导致它抛出异常,或者它对我的代码的影响可能会导致我抛出异常,而且可能在许多不同的地方。 一般来说,我不相信当前方法之外的代码能够自行运行。
为了便于论证,假设我的方法有 11 个退出点,其中 10 个位于此 using 块内,1 个位于其后面(这在我编写的某些库代码中可能很常见)。
由于退出 using 块时会自动释放该对象,因此我不需要 10 次不同的 .Dispose() 调用——它就这样发生了。 这会产生更干净的代码,因为现在处理调用不再那么混乱(在本例中减少了大约 10 行代码)。
如果有人在我之后更改了代码,如果他们忘记调用 dispose,那么引入 IDisposable 泄漏错误(这可能很耗时)的风险也较小,因为 using 块不需要这样做。
I use using blocks primarily for this scenario:
I'm consuming some external object (usually an IDisposable wrapped COM object in my case). The state of the object itself may cause it to throw an exception or how it affects my code may cause me to throw an exception, and perhaps in many different places. In general, I trust no code outside of my current method to behave itself.
For the sake of argument, lets say I have 11 exit points to my method, 10 of which are inside this using block and 1 after it (which can be typical in some library code I've written).
Since the object is automatically disposed of when exiting the using block, I don't need to have 10 different .Dispose() calls--it just happens. This results in cleaner code, since it is now less cluttered with dispose calls (~10 fewer lines of code in this case).
There is also less risk of introducing IDisposable leak bugs (which can be time consuming to find) by somebody altering the code after me if they forget to call dispose, because it isn't necessary with a using block.
与 Fxcop(与其相关)一样,VS 中的代码分析工具(如果您有更高版本之一)也会找到这些情况。
Like Fxcop (to which they're related), the code analysis tools in VS (if you have one of the higher-up editions) will find these cases too.
始终尝试使用“using”块。 对于大多数对象来说,这没有太大区别,但我最近遇到了一个问题,我在类中实现了 ActiveX 控件,并且除非正确调用 Dispose,否则无法正常清理。 最重要的是,即使它看起来没有多大影响,也要尝试正确地做,因为有时它会产生影响。
Always try to use the "using" blocks. For most objects, it doesn't make a big difference however I encountered a recent issue where I implemented an ActiveX control in a class and in didn't clean up gracefully unless the Dispose was called correctly. The bottom line is even if it doesn't seem to make much of a difference, try to do it correctly because some time it will make a difference.
根据此链接CodeRush 加载项 将在您键入时实时检测并标记本地 IDisposable 变量未清理的情况。
可以在你的追求中途遇见你。
According to this link the CodeRush add-in will detect and flag when local IDisposable variables aren't cleaned up, in real-time, as you type.
Could meet you halfway on your quest.
我没明白你问题的重点。 多亏了垃圾收集器,内存泄漏几乎不可能发生。 但是,您需要一些强大的逻辑。
我通常这样创建 IDisposable 类:
现在,即使您错过使用 using 语句,该对象最终也会被垃圾收集,并执行正确的销毁逻辑。 您可以停止线程,结束连接,保存数据,无论您需要什么(在 此示例,我取消订阅远程服务并在需要时执行远程删除调用)
[编辑]显然,尽快调用 Dispose 有助于提高应用程序性能,并且是一个很好的做法。 但是,由于我的示例,如果您忘记调用 Dispose,它最终会被调用并清理对象。
I'm not getting the point of your question. Thanks to the garbage collector, memory leaks are close to impossible to occur. However, you need some robust logic.
I use to create
IDisposable
classes like this:Now, even if you miss to use
using
statement, the object will be eventually garbage-collected and proper destruction logic is executed. You may stop threads, end connections, save data, whatever you need (in this example, I unsubscribe from a remote service and perform a remote delete call if needed)[Edit] obviously, calling Dispose as soon as possible helps application performance, and is a good practice. But, thanks to my example, if you forget to call Dispose it will be eventually called and the object is cleaned up.
FxCop可能有帮助(尽管它没有发现我刚刚向它发起的测试); 但是是的:你应该检查一下。
IDisposable
是系统的重要组成部分,您需要养成这个习惯。 使用智能感知来查找.D
是一个好的开始(尽管并不完美)。然而,不需要很长时间就能熟悉需要处理的类型; 例如,通常涉及任何外部事物(连接、文件、数据库)。
ReSharper 也可以完成这项工作,提供“投入使用构造”选项。 不过,它不会将其作为错误提供...当然
,如果您不确定 - 尝试
使用
它:如果您不确定,编译器会嘲笑您偏执:FxCop might help (although it didn't spot a test I just fired at it); but yes: you are meant to check.
IDisposable
is simply such an important part of the system that you need to get into this habit. Using intellisense to look for.D
is a good start (though not perfect).However, it doesn't take long to familiarize yourself with types that need disposal; generally anything involving anything external (connection, file, database), for example.
ReSharper does the job too, offering a "put into using construct" option. It doesn't offer it as an error, though...
Of course, if you are unsure - try
using
it: the compiler will laugh mockingly at you if you are being paranoid: