C# 正确使用 IDisposable 接口

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/538060/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 07:17:04  来源:igfitidea点击:

Proper use of the IDisposable interface

c#.netgarbage-collectionidisposable

提问by cwick

I know from reading the Microsoft documentationthat the "primary" use of the IDisposableinterface is to clean up unmanaged resources.

我从阅读Microsoft 文档中了解到,该IDisposable界面的“主要”用途是清理非托管资源。

To me, "unmanaged" means things like database connections, sockets, window handles, etc. But, I've seen code where the Dispose()method is implemented to free managedresources, which seems redundant to me, since the garbage collector should take care of that for you.

对我来说,“非托管”意味着诸如数据库连接、套接字、窗口句柄等。但是,我已经看到Dispose()实现该方法以释放托管资源的代码,这对我来说似乎是多余的,因为垃圾收集器应该负责给你的。

For example:

例如:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

My question is, does this make the garbage collector free memory used by MyCollectionany faster than it normally would?

我的问题是,这是否会使垃圾收集器使用的空闲内存MyCollection比平时更快?

edit: So far people have posted some good examples of using IDisposable to clean up unmanaged resources such as database connections and bitmaps. But suppose that _theListin the above code contained a million strings, and you wanted to free that memory now, rather than waiting for the garbage collector. Would the above code accomplish that?

编辑:到目前为止,人们已经发布了一些使用 IDisposable 清理非托管资源(如数据库连接和位图)的好例子。但是假设_theList在上面的代码中包含上百万字符串,你想释放内存现在,而不是等待垃圾回收器。上面的代码能做到吗?

采纳答案by Ian Boyd

The point of Dispose isto free unmanaged resources. It needs to be done at some point, otherwise they will never be cleaned up. The garbage collector doesn't know howto call DeleteHandle()on a variable of type IntPtr, it doesn't know whetheror not it needs to call DeleteHandle().

Dispose 的目的释放非托管资源。它需要在某个时候完成,否则它们将永远不会被清理。垃圾收集器不知道如何调用DeleteHandle()类型为 的变量,IntPtr也不知道是否需要调用DeleteHandle().

Note: What is an unmanaged resource? If you found it in the Microsoft .NET Framework: it's managed. If you went poking around MSDN yourself, it's unmanaged. Anything you've used P/Invoke calls to get outside of the nice comfy world of everything available to you in the .NET Framework is unmanaged – and you're now responsible for cleaning it up.

注意:什么是非托管资源?如果您在 Microsoft .NET Framework 中找到它:它是托管的。如果你自己去探索 MSDN,它是不受管理的。您使用 P/Invoke 调用摆脱 .NET Framework 中所有可用内容的舒适世界的任何内容都是非托管的——您现在负责清理它。

The object that you've created needs to expose somemethod, that the outside world can call, in order to clean up unmanaged resources. The method can be named whatever you like:

您创建的对象需要公开一些外部世界可以调用的方法,以便清理非托管资源。该方法可以随意命名:

public void Cleanup()

or

或者

public void Shutdown()

But instead there is a standardized name for this method:

但是,此方法有一个标准化名称:

public void Dispose()

There was even an interface created, IDisposable, that has just that one method:

甚至创建了一个接口IDisposable,它只有一个方法:

public interface IDisposable
{
   void Dispose()
}

So you make your object expose the IDisposableinterface, and that way you promise that you've written that single method to clean up your unmanaged resources:

所以你让你的对象公开IDisposable接口,这样你就保证你已经编写了一个方法来清理你的非托管资源:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

And you're done. Except you can do better.

你已经完成了。除了你可以做得更好。



What if your object has allocated a 250MB System.Drawing.Bitmap(i.e. the .NET managed Bitmap class) as some sort of frame buffer? Sure, this is a managed .NET object, and the garbage collector will free it. But do you really want to leave 250MB of memory just sitting there – waiting for the garbage collector to eventuallycome along and free it? What if there's an open database connection? Surely we don't want that connection sitting open, waiting for the GC to finalize the object.

如果您的对象分配了一个 250MB System.Drawing.Bitmap(即 .NET 托管 Bitmap 类)作为某种帧缓冲区怎么办?当然,这是一个托管的 .NET 对象,垃圾收集器将释放它。但是你真的想留下 250MB 的内存只是坐在那里——等待垃圾收集器最终出现并释放它吗?如果有一个打开的数据库连接怎么办?当然,我们不希望该连接处于打开状态,等待 GC 完成对象。

If the user has called Dispose()(meaning they no longer plan to use the object) why not get rid of those wasteful bitmaps and database connections?

如果用户已经调用Dispose()(意味着他们不再计划使用该对象)为什么不摆脱那些浪费的位图和数据库连接?

So now we will:

所以现在我们将:

  • get rid of unmanaged resources (because we have to), and
  • get rid of managed resources (because we want to be helpful)
  • 摆脱非托管资源(因为我们必须这样做),以及
  • 摆脱托管资源(因为我们想提供帮助)

So let's update our Dispose()method to get rid of those managed objects:

因此,让我们更新我们的Dispose()方法以摆脱这些托管对象:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

And all is good, except you can do better!

一切都很好,除了你可以做得更好



What if the person forgotto call Dispose()on your object? Then they would leak some unmanagedresources!

如果这个人忘记调用Dispose()你的对象怎么办?然后他们会泄漏一些非托管资源!

Note:They won't leak managedresources, because eventually the garbage collector is going to run, on a background thread, and free the memory associated with any unused objects. This will include your object, and any managed objects you use (e.g. the Bitmapand the DbConnection).

注意:它们不会泄漏托管资源,因为最终垃圾收集器将在后台线程上运行,并释放与任何未使用对象关联的内存。这将包括您的对象以及您使用的任何托管对象(例如BitmapDbConnection)。

If the person forgot to call Dispose(), we can stillsave their bacon! We still have a way to call it forthem: when the garbage collector finally gets around to freeing (i.e. finalizing) our object.

如果那个人忘记打电话Dispose(),我们仍然可以保存他们的培根!我们仍然有办法它们调用它:当垃圾收集器最终开始释放(即完成)我们的对象时。

Note:The garbage collector will eventually free all managed objects. When it does, it calls the Finalizemethod on the object. The GC doesn't know, or care, about yourDisposemethod. That was just a name we chose for a method we call when we want to get rid of unmanaged stuff.

注意:垃圾收集器最终会释放所有托管对象。当它这样做时,它调用Finalize对象上的方法。GC 不知道或不关心您的Dispose方法。这只是我们为要摆脱非托管内容时调用的方法选择的名称。

The destruction of our object by the Garbage collector is the perfecttime to free those pesky unmanaged resources. We do this by overriding the Finalize()method.

垃圾收集器销毁我们的对象是释放那些讨厌的非托管资源的最佳时机。我们通过覆盖该Finalize()方法来做到这一点。

Note:In C#, you don't explicitly override the Finalize()method. You write a method that looks likea C++ destructor, and the compiler takes that to be your implementation of the Finalize()method:

注意:在 C# 中,您不会显式覆盖该Finalize()方法。你写一个方法看起来像一个C ++的析构函数,编译器采用的是成为您的实现Finalize()方法:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

But there's a bug in that code. You see, the garbage collector runs on a background thread; you don't know the order in which two objects are destroyed. It is entirely possible that in your Dispose()code, the managedobject you're trying to get rid of (because you wanted to be helpful) is no longer there:

但是该代码中有一个错误。你看,垃圾收集器在后台线程上运行;您不知道销毁两个对象的顺序。完全有可能在您的Dispose()代码中,您试图摆脱的托管对象(因为您想提供帮助)不再存在:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

So what you need is a way for Finalize()to tell Dispose()that it should not touch any managedresources (because they might not be thereanymore), while still freeing unmanaged resources.

所以你需要的是一种方法Finalize()来告诉Dispose()它不应该触及任何托管资源(因为它们可能不再存在),同时仍然释放非托管资源。

The standard pattern to do this is to have Finalize()and Dispose()both call a third(!) method; where you pass a Boolean saying if you're calling it from Dispose()(as opposed to Finalize()), meaning it's safe to free managed resources.

在标准模式要做到这一点是有Finalize()Dispose()两个呼叫的第三个方法(!); 如果您从Dispose()(而不是Finalize())调用它,则传递一个布尔值,这意味着释放托管资源是安全的。

This internalmethod couldbe given some arbitrary name like "CoreDispose", or "MyInternalDispose", but is tradition to call it Dispose(Boolean):

这个内部方法可以被赋予一些任意名称,如“CoreDispose”或“MyInternalDispose”,但传统上调用它Dispose(Boolean)

protected void Dispose(Boolean disposing)

But a more helpful parameter name might be:

但更有用的参数名称可能是:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

And you change your implementation of the IDisposable.Dispose()method to:

您将IDisposable.Dispose()方法的实现更改为:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

and your finalizer to:

和你的终结者:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Note: If your object descends from an object that implements Dispose, then don't forget to call their baseDispose method when you override Dispose:

注意:如果您的对象是从一个实现 的对象派生而来的Dispose,那么在您覆盖 Dispose 时不要忘记调用它们的基本Dispose 方法:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

And all is good, except you can do better!

一切都很好,除了你可以做得更好



If the user calls Dispose()on your object, then everything has been cleaned up. Later on, when the garbage collector comes along and calls Finalize, it will then call Disposeagain.

如果用户调用Dispose()您的对象,则一切都已清理完毕。稍后,当垃圾收集器出现并调用 Finalize 时,它​​将Dispose再次调用。

Not only is this wasteful, but if your object has junk references to objects you already disposed of from the lastcall to Dispose(), you'll try to dispose them again!

这不仅是浪费,而且如果您的对象具有对您在上次调用 时已处理的对象的垃圾引用Dispose(),您将尝试再次处理它们!

You'll notice in my code I was careful to remove references to objects that I've disposed, so I don't try to call Disposeon a junk object reference. But that didn't stop a subtle bug from creeping in.

您会注意到,在我的代码中,我小心地删除了对我已处理的Dispose对象的引用,因此我不会尝试调用垃圾对象引用。但这并没有阻止一个微妙的错误潜入。

When the user calls Dispose(): the handle CursorFileBitmapIconServiceHandleis destroyed. Later when the garbage collector runs, it will try to destroy the same handle again.

当用户调用时Dispose():句柄CursorFileBitmapIconServiceHandle被销毁。稍后当垃圾收集器运行时,它会再次尝试销毁同一个句柄。

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

The way you fix this is tell the garbage collector that it doesn't need to bother finalizing the object – its resources have already been cleaned up, and no more work is needed. You do this by calling GC.SuppressFinalize()in the Dispose()method:

解决这个问题的方法是告诉垃圾收集器它不需要费心完成对象——它的资源已经被清理干净,不需要更多的工作。您可以通过调用做到这一点GC.SuppressFinalize()Dispose()方法:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Now that the user has called Dispose(), we have:

现在用户已经调用了Dispose(),我们有:

  • freed unmanaged resources
  • freed managed resources
  • 释放非托管资源
  • 释放托管资源

There's no point in the GC running the finalizer – everything's taken care of.

GC 运行终结器毫无意义——一切都已处理完毕。

Couldn't I use Finalize to cleanup unmanaged resources?

我不能使用 Finalize 来清理非托管资源吗?

The documentation for Object.Finalizesays:

的文档Object.Finalize说:

The Finalize method is used to perform cleanup operations on unmanaged resources held by the current object before the object is destroyed.

Finalize 方法用于在对象被销毁之前对当前对象持有的非托管资源执行清理操作。

But the MSDN documentation also says, for IDisposable.Dispose:

但是 MSDN 文档也说,对于IDisposable.Dispose

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

执行与释放、释放或重置非托管资源相关的应用程序定义的任务。

So which is it? Which one is the place for me to cleanup unmanaged resources? The answer is:

那么它是哪个?哪个是我清理非托管资源的地方?答案是:

It's your choice! But choose Dispose.

这是你的选择!但是选择Dispose

You certainly could place your unmanaged cleanup in the finalizer:

您当然可以将非托管清理放在终结器中:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

The problem with that is you have no idea when the garbage collector will get around to finalizing your object. Your un-managed, un-needed, un-used native resources will stick around until the garbage collector eventuallyruns. Then it will call your finalizer method; cleaning up unmanaged resources. The documentation of Object.Finalizepoints this out:

问题是你不知道垃圾收集器什么时候会完成你的对象。您未管理、不需要、未使用的本机资源将一直存在,直到垃圾收集器最终运行。然后它会调用你的终结器方法;清理非托管资源。Object.Finalize的文档指出了这一点:

The exact time when the finalizer executes is undefined. To ensure deterministic release of resources for instances of your class, implement a Closemethod or provide a IDisposable.Disposeimplementation.

终结器执行的确切时间是不确定的。为了确保您的类实例资源的确定性释放,请实现Close方法或提供一个IDisposable.Dispose实现。

This is the virtue of using Disposeto cleanup unmanaged resources; you get to know, and control, when unmanaged resource are cleaned up. Their destruction is "deterministic".

这是Dispose用于清理非托管资源的优点;您可以了解和控制何时清理非托管资源。它们的破坏是“确定性的”



To answer your original question: Why not release memory now, rather than for when the GC decides to do it? I have a facial recognition software that needsto get rid of 530 MB of internal images now, since they're no longer needed. When we don't: the machine grinds to a swapping halt.

回答您最初的问题:为什么不现在释放内存,而不是在 GC 决定时释放内存?我有一个面部识别软件,需要摆脱530 MB的内部图像的现在,因为他们不再需要。如果我们不这样做:机器会停止交换。

Bonus Reading

奖励阅读

For anyone who likes the style of this answer (explaining the why, so the howbecomes obvious), I suggest you read Chapter One of Don Box's Essential COM:

对于喜欢这个答案风格的人(解释为什么,所以如何变得明显),我建议你阅读 Don Box 的 Essential COM 的第一章:

In 35 pages he explains the problems of using binary objects, and invents COM before your eyes. Once you realize the whyof COM, the remaining 300 pages are obvious, and just detail Microsoft's implementation.

在 35 页中,他解释了使用二进制对象的问题,并在您眼前发明了 COM。一旦你了解了COM的原因,剩下的300页就很明显了,只是详细说明了微软的实现。

I think every programmer who has ever dealt with objects or COM should, at the very least, read the first chapter. It is the best explanation of anything ever.

我认为每个曾经处理过对象或 COM 的程序员至少应该阅读第一章。这是对任何事物的最好解释。

Extra Bonus Reading

额外奖励阅读

When everything you know is wrongby Eric Lippert

当你知道的一切都是错误的时埃里克·利珀特

It is therefore very difficult indeed to write a correct finalizer, and the best advice I can give you is to not try.

因此,编写正确的终结器确实非常困难,我能给你的最好建议是不要尝试

回答by mqp

Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear()calls.

是的,该代码是完全多余和不必要的,它不会让垃圾收集器做任何它不会做的事情(一旦 MyCollection 的实例超出范围,即。)尤其是.Clear()调用。

Answer to your edit: Sort of. If I do this:

回答您的编辑:有点。如果我这样做:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

It's functionally identical to this for purposes of memory management:

出于内存管理的目的,它在功能上与此相同:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

If you really really really need to free the memory this very instant, call GC.Collect(). There's no reason to do this here, though. The memory will be freed when it's needed.

如果您真的真的需要立即释放内存,请调用GC.Collect(). 不过,没有理由在这里这样做。内存将在需要时释放。

回答by yfeldblum

IDisposableis often used to exploit the usingstatement and take advantage of an easy way to do deterministic cleanup of managed objects.

IDisposable通常用于利用using语句并利用一种简单的方法对托管对象进行确定性清理。

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

回答by Drew Noakes

If MyCollectionis going to be garbage collected anyway, then you shouldn't need to dispose it. Doing so will just churn the CPU more than necessary, and may even invalidate some pre-calculated analysis that the garbage collector has already performed.

如果MyCollection无论如何都要进行垃圾收集,那么您不需要处理它。这样做只会过多地搅动 CPU,甚至可能会使垃圾收集器已经执行的一些预先计算的分析无效。

I use IDisposableto do things like ensure threads are disposed correctly, along with unmanaged resources.

IDisposable过去常常做一些事情,例如确保正确处理线程以及非托管资源。

EDITIn response to Scott's comment:

编辑回应斯科特的评论:

The only time the GC performance metrics are affected is when a call the [sic] GC.Collect() is made"

GC 性能指标唯一受到影响的时间是调用 [sic] GC.Collect() 时”

Conceptually, the GC maintains a view of the object reference graph, and all references to it from the stack frames of threads. This heap can be quite large and span many pages of memory. As an optimisation, the GC caches its analysis of pages that are unlikely to change very often to avoid rescanning the page unnecessarily. The GC receives notification from the kernel when data in a page changes, so it knows that the page is dirty and requires a rescan. If the collection is in Gen0 then it's likely that other things in the page are changing too, but this is less likely in Gen1 and Gen2. Anecdotally, these hooks were not available in Mac OS X for the team who ported the GC to Mac in order to get the Silverlight plug-in working on that platform.

从概念上讲,GC 维护对象引用图的视图,以及来自线程堆栈帧的所有引用。这个堆可以非常大并且跨越许多内存页。作为一种优化,GC 缓存其对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生变化时,GC 会收到来自内核的通知,因此它知道该页面是脏的,需要重新扫描。如果集合在 Gen0 中,那么页面中的其他内容很可能也在更改,但在 Gen1 和 Gen2 中的可能性较小。有趣的是,为了让 Silverlight 插件在该平台上工作,将 GC 移植到 Mac 的团队在 Mac OS X 中没有这些钩子。

Another point against unnecessary disposal of resources: imagine a situation where a process is unloading. Imagine also that the process has been running for some time. Chances are that many of that process's memory pages have been swapped to disk. At the very least they're no longer in L1 or L2 cache. In such a situation there is no point for an application that's unloading to swap all those data and code pages back into memory to 'release' resources that are going to be released by the operating system anyway when the process terminates. This applies to managed and even certain unmanaged resources. Only resources that keep non-background threads alive must be disposed, otherwise the process will remain alive.

反对不必要的资源处置的另一点:想象一个进程正在卸载的情况。还想象一下,该进程已经运行了一段时间。很可能该进程的许多内存页已交换到磁盘。至少它们不再在 L1 或 L2 缓存中。在这种情况下,正在卸载的应用程序将所有这些数据和代码页交换回内存以“释放”操作系统将在进程终止时释放的资源是没有意义的。这适用于托管资源甚至某些非托管资源。只有保持非后台线程处于活动状态的资源必须被释放,否则进程将保持活动状态。

Now, during normal execution there are ephemeral resources that must be cleaned up correctly (as @fezmonkey points out database connections, sockets, window handles) to avoid unmanaged memory leaks. These are the kinds of things that have to be disposed. If you create some class that owns a thread (and by owns I mean that it created it and therefore is responsible for ensuring it stops, at least by my coding style), then that class most likely must implement IDisposableand tear down the thread during Dispose.

现在,在正常执行期间,必须正确清理临时资源(如@fezmonkey 指出的数据库连接、套接字、窗口句柄)以避免非托管内存泄漏。这些是必须处理的东西。如果您创建了某个拥有线程的类(并且拥有我的意思是它创建了它,因此负责确保它停止,至少按照我的编码风格),那么该类很可能必须IDisposableDispose.

The .NET framework uses the IDisposableinterface as a signal, even warning, to developers that the this class mustbe disposed. I can't think of any types in the framework that implement IDisposable(excluding explicit interface implementations) where disposal is optional.

.NET 框架使用该IDisposable接口作为信号,甚至警告,向开发人员表明必须处置此类。我想不出框架中实现IDisposable(不包括显式接口实现)的任何类型,其中处置是可选的。

回答by Scott Dorman

The purpose of the Dispose pattern is to provide a mechanism to clean up both managed and unmanaged resources and when that occurs depends on how the Dispose method is being called. In your example, the use of Dispose is not actually doing anything related to dispose, since clearing a list has no impact on that collection being disposed. Likewise, the calls to set the variables to null also have no impact on the GC.

Dispose 模式的目的是提供一种机制来清理托管和非托管资源,何时发生取决于调用 Dispose 方法的方式。在您的示例中,使用 Dispose 实际上并没有做任何与 dispose 相关的事情,因为清除列表对正在处理的集合没有影响。同样,将变量设置为 null 的调用也对 GC 没有影响。

You can take a look at this articlefor more details on how to implement the Dispose pattern, but it basically looks like this:

你可以看看这篇文章,了解更多关于如何实现 Dispose 模式的细节,但它基本上是这样的:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

The method that is the most important here is the Dispose(bool), which actually runs under two different circumstances:

这里最重要的方法是 Dispose(bool),它实际上在两种不同的情况下运行:

  • disposing == true: the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed.
  • disposing == false: the method has been called by the runtime from inside the finalizer, and you should not reference other objects. Only unmanaged resources can be disposed.
  • disposing == true:该方法已被用户代码直接或间接调用。可以处置托管和非托管资源。
  • disposing == false:该方法已由运行时从终结器内部调用,您不应引用其他对象。只能处理非托管资源。

The problem with simply letting the GC take care of doing the cleanup is that you have no real control over when the GC will run a collection cycle (you can call GC.Collect(), but you really shouldn't) so resources may stay around longer than needed. Remember, calling Dispose() doesn't actually cause a collection cycle or in any way cause the GC to collect/free the object; it simply provides the means to more deterministicly cleanup the resources used and tell the GC that this cleanup has already been performed.

简单地让 GC 负责清理的问题在于,您无法真正控制 GC 何时运行收集周期(您可以调用 GC.Collect(),但您确实不应该)因此资源可能会保留比需要的时间长。请记住,调用 Dispose() 实际上不会导致收集周期或以任何方式导致 GC 收集/释放对象;它只是提供了更确定地清理所用资源的方法,并告诉 GC 已执行此清理。

The whole point of IDisposable and the dispose pattern isn't about immediately freeing memory. The only time a call to Dispose will actually even have a chance of immediately freeing memory is when it is handling the disposing == false scenario and manipulating unmanaged resources. For managed code, the memory won't actually be reclaimed until the GC runs a collection cycle, which you really have no control over (other than calling GC.Collect(), which I've already mentioned is not a good idea).

IDisposable 和处置模式的重点不是立即释放内存。对 Dispose 的调用实际上甚至有机会立即释放内存的唯一时间是在它处理 disposing == false 场景和操作非托管资源时。对于托管代码,在 GC 运行收集周期之前实际上不会回收内存,您实际上无法控制(除了调用 GC.Collect(),我已经提到这不是一个好主意)。

Your scenario isn't really valid since strings in .NET don't use any unamanged resources and don't implement IDisposable, there is no way to force them to be "cleaned up."

您的方案并不真正有效,因为 .NET 中的字符串不使用任何未管理的资源并且不实现 IDisposable,因此无法强制“清理”它们。

回答by Michael Burr

There are things that the Dispose()operation does in the example code that mighthave an effect that would not occur due to a normal GC of the MyCollectionobject.

Dispose()示例代码中的操作执行的某些操作可能会产生由于MyCollection对象的正常 GC 不会发生的影响。

If the objects referenced by _theListor _theDictare referred to by other objects, then that List<>or Dictionary<>object will not be subject to collection but will suddenly have no contents. If there were no Dispose() operation as in the example, those collections would still contain their contents.

如果对象被其他对象引用_theList或被_theDict其他对象引用,则该对象List<>Dictionary<>对象将不会被收集,而是会突然没有内容。如果没有示例中的 Dispose() 操作,这些集合仍将包含其内容。

Of course, if this were the situation I would call it a broken design - I'm just pointing out (pedantically, I suppose) that the Dispose()operation might not be completely redundant, depending on whether there are other uses of the List<>or Dictionary<>that are not shown in the fragment.

当然,如果是这样的情况我会称之为一个破碎的设计-我只是指出(迂腐,我想),该Dispose()操作可能不完全是多余的,这取决于是否存在的其他用途List<>Dictionary<>不属于片段中显示。

回答by olli-MSFT

Scenarios I make use of IDisposable: clean up unmanaged resources, unsubscribe for events, close connections

我使用 IDisposable 的场景:清理非托管资源、取消订阅事件、关闭连接

The idiom I use for implementing IDisposable (not threadsafe):

我用于实现 IDisposable (不是线程安全)的习惯用法:

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

回答by Daniel Earwicker

There should be no further calls to an object's methods after Dispose has been called on it (although an object should tolerate further calls to Dispose). Therefore the example in the question is silly. If Dispose is called, then the object itself can be discarded. So the user should just discard all references to that whole object (set them to null) and all the related objects internal to it will automatically get cleaned up.

在对对象调用 Dispose 之后,不应再调用该对象的方法(尽管对象应容忍对 Dispose 的进一步调用)。因此,问题中的示例很愚蠢。如果调用 Dispose,则可以丢弃对象本身。所以用户应该丢弃对整个对象的所有引用(将它们设置为 null),并且它内部的所有相关对象将自动被清除。

As for the general question about managed/unmanaged and the discussion in other answers, I think any answer to this question has to start with a definition of an unmanaged resource.

至于关于托管/非托管的一般问题以及其他答案中的讨论,我认为对这个问题的任何答案都必须从非托管资源的定义开始。

What it boils down to is that there is a function you can call to put the system into a state, and there's another function you can call to bring it back out of that state. Now, in the typical example, the first one might be a function that returns a file handle, and the second one might be a call to CloseHandle.

归结为,您可以调用一个函数将系统置于某种状态,并且您可以调用另一个函数将其从该状态中恢复。现在,在典型示例中,第一个可能是返回文件句柄的函数,第二个可能是对CloseHandle.

But - and this is the key - they could be any matching pair of functions. One builds up a state, the other tears it down. If the state has been built but not torn down yet, then an instance of the resource exists. You have to arrange for the teardown to happen at the right time - the resource is not managed by the CLR. The only automatically managed resource type is memory. There are two kinds: the GC, and the stack. Value types are managed by the stack (or by hitching a ride inside reference types), and reference types are managed by the GC.

但是——这是关键——它们可以是任何匹配的函数对。一个建立一个国家,另一个摧毁它。如果状态已建立但尚未拆除,则存在资源的一个实例。您必须安排在正确的时间进行拆卸 - 资源不由 CLR 管理。唯一自动管理的资源类型是内存。有两种:GC 和堆栈。值类型由堆栈管理(或通过在引用类型中搭便车),而引用类型由 GC 管理。

These functions may cause state changes that can be freely interleaved, or may need to be perfectly nested. The state changes may be threadsafe, or they might not.

这些函数可能会导致可以自由交错的状态变化,或者可能需要完美嵌套。状态更改可能是线程安全的,也可能不是。

Look at the example in Justice's question. Changes to the Log file's indentation must be perfectly nested, or it all goes wrong. Also they are unlikely to be threadsafe.

看看司法问题中的例子。对日志文件缩进的更改必须完全嵌套,否则一切都会出错。它们也不太可能是线程安全的。

It is possible to hitch a ride with the garbage collector to get your unmanaged resources cleaned up. But only if the state change functions are threadsafe and two states can have lifetimes that overlap in any way. So Justice's example of a resource must NOT have a finalizer! It just wouldn't help anyone.

可以搭便车使用垃圾收集器来清理您的非托管资源。但前提是状态更改函数是线程安全的,并且两个状态的生命周期可以以任何方式重叠。因此,Justice 的资源示例不能有终结器!它只是不会帮助任何人。

For those kinds of resources, you can just implement IDisposable, without a finalizer. The finalizer is absolutely optional - it has to be. This is glossed over or not even mentioned in many books.

对于这些类型的资源,您可以只实现IDisposable,而无需终结器。终结器绝对是可选的 - 它必须是。这在许多书中被掩盖或什至没有提及。

You then have to use the usingstatement to have any chance of ensuring that Disposeis called. This is essentially like hitching a ride with the stack (so as finalizer is to the GC, usingis to the stack).

然后,您必须使用该using语句来确保Dispose调用该语句。这本质上就像搭便车一样(就像终结器之于 GC,using是之于堆栈)。

The missing part is that you have to manually write Dispose and make it call onto your fields and your base class. C++/CLI programmers don't have to do that. The compiler writes it for them in most cases.

缺少的部分是您必须手动编写 Dispose 并使其调用您的字段和基类。C++/CLI 程序员不必这样做。在大多数情况下,编译器会为它们编写。

There is an alternative, which I prefer for states that nest perfectly and are not threadsafe (apart from anything else, avoiding IDisposable spares you the problem of having an argument with someone who can't resist adding a finalizer to every class that implements IDisposable).

还有一种替代方法,我更喜欢完美嵌套且不是线程安全的状态(除此之外,避免 IDisposable 可以避免与无法抗拒向实现 IDisposable 的每个类添加终结器的人争论的问题) .

Instead of writing a class, you write a function. The function accepts a delegate to call back to:

不是编写类,而是编写函数。该函数接受一个委托回调:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

And then a simple example would be:

然后一个简单的例子是:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

The lambda being passed in serves as a code block, so it's like you make your own control structure to serve the same purpose as using, except that you no longer have any danger of the caller abusing it. There's no way they can fail to clean up the resource.

传入的 lambda 用作代码块,因此就像您制作自己的控制结构以实现与 相同的目的using,只是您不再有调用者滥用它的任何危险。他们不可能无法清理资源。

This technique is less useful if the resource is the kind that may have overlapping lifetimes, because then you want to be able to build resource A, then resource B, then kill resource A and then later kill resource B. You can't do that if you've forced the user to perfectly nest like this. But then you need to use IDisposable(but still without a finalizer, unless you have implemented threadsafety, which isn't free).

如果资源是可能具有重叠生命周期的类型,则此技术不太有用,因为那样您希望能够构建资源 A,然后是资源 B,然后杀死资源 A,然后再杀死资源 B。你不能那样做如果你强迫用户像这样完美嵌套。但是你需要使用IDisposable(但仍然没有终结器,除非你已经实现了线程安全,这不是免费的)。

回答by Arjan Einbu

If anything, I'd expect the code to be lessefficient than when leaving it out.

如果有什么事情,我期望的代码要留出来时相比,效率更高。

Calling the Clear() methods are unnecessary, and the GC probably wouldn't do that if the Dispose didn't do it...

调用 Clear() 方法是不必要的,如果 Dispose 不这样做,GC 可能不会这样做......

回答by Robert Paulson

In the example you posted, it still doesn't "free the memory now". All memory is garbage collected, but it may allow the memory to be collected in an earlier generation. You'd have to run some tests to be sure.

在您发布的示例中,它仍然没有“立即释放内存”。所有内存都被垃圾收集,但它可能允许在较早的一代中收集内存。您必须运行一些测试才能确定。



The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.

框架设计指南是指导方针,而不是规则。它们会告诉您界面的主要用途、何时使用它、如何使用它以及何时不使用它。

I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commitcall never happened it would then call Rollbackon itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:

我曾经阅读过使用 IDisposable 在失败时执行简单的 RollBack() 的代码。下面的 MiniTx 类将检查 Dispose() 上的标志,如果Commit调用从未发生过,它将调用Rollback自身。它添加了一个间接层,使调用代码更容易理解和维护。结果看起来像:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

I've also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.

我还看到计时/日志代码做同样的事情。在这种情况下, Dispose() 方法停止计时器并记录块已退出。

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

So here are a couple of concrete examples that don't do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.

所以这里有几个具体的例子,它们不做任何非托管资源清理,但成功地使用 IDisposable 创建更干净的代码。