Should “Dispose” only be used for types containing unmanaged resources?

五迷三道 提交于 2019-11-26 15:52:01

问题


I was having a discussion with a colleague recently about the value of Dispose and types that implement IDisposable.

I think there is value in implementing IDisposable for types that should clean up as soon as possible, even if there are no unmanaged resources to clean up.

My colleague thinks differently; implementing IDisposable if you don't have any unmanaged resources isn't necessary as your type will eventually be garbage collected.

My argument was that if you had an ADO.NET connection that you wanted to close as soon as possible, then implementing IDisposable and using new MyThingWithAConnection() would make sense. My colleage replied that, under the covers, an ADO.NET connection is a unmanaged resource. My reply to his reply was that everything ultimately is an unmanaged resource.

I am aware of the recommended disposable pattern where you free managed and unmanaged resources if Dispose is called but only free unmanaged resources if called via the finalizer/destructor (and blogged a while ago about how to alert consumers of improper use of your IDisposable types)

So, my question is, if you've got a type that doesn't contain unmanaged resources, is it worth implementing IDisposable?


回答1:


There are different valid uses for IDisposable. A simple example is holding an open file, which you need to be closed at certain moment, as soon as you don't need it any more. Of course, you could provide a method Close, but having it in Dispose and using pattern like using (var f = new MyFile(path)) { /*process it*/ } would be more exception-safe.

A more popular example would be holding some other IDisposable resources, which usually means that you need to provide your own Dispose in order to dispose them as well.

In general, as soon as you want to have deterministic destruction of anything, you need to implement IDisposable.

The difference between my opinion and yours is that I implement IDisposable as soon as some resource needs deterministic destruction/freeing, not necessary as soon as possible. Relying on garbage collection is not an option in this case (contrary to your colleague's claim), because it happens at unpredictable moment of time, and actually may not happen at all!

The fact that any resource is unmanaged under the cover really doesn't mean anything: the developer should think in terms of "when and how is it right to dispose of this object" rather than "how does it work under the cover". The underlying implementation may change with the time anyway.

In fact, one of the main differences between C# and C++ is the absence of default deterministic destruction. The IDisposable comes to close the gap: you can order the deterministic destruction (although you cannot ensure the clients are calling it; the same way in C++ you cannot be sure that the clients call delete on the object).


Small addition: what is actually the difference between the deterministic freeing the resources and freeing them as soon as possible? Actually, those are different (though not completely orthogonal) notions.

If the resources are to be freed deterministically, this means that the client code should have a possibility to say "Now, I want this resource freed". This may be actually not the earliest possible moment when the resource may be freed: the object holding the resource might have got everything it needs from the resource, so potentially it could free the resource already. On the other hand, the object might choose to keep the (usually unmanaged) resource even after the object's Dispose ran through, cleaning it up only in finalizer (if holding the resource for too long time doesn't make any problem).

So, for freeing the resource as soon as possible, strictly speaking, Dispose is not necessary: the object may free the resource as soon as it realizes itself that the resource is not needed any more. Dispose however serves as a useful hint that the object itself is not needed any more, so perhaps the resources may be freed at that point if appropriate.


One more necessary addition: it's not only unmanaged resources that need deterministic deallocation! This seems to be one of key points of the difference in opinions among the answers to this question. One can have purely imaginative construct, which may need to be freed deterministically.

Examples are: a right to access some shared structure (think RW-lock), a huge memory chunk (imagine that you are managing some of the program's memory manually), a license for using some other program (imagine that you are not allowed to run more than X copies of some program simultaneously), etc. Here the object to be freed is not an unmanaged resource, but a right to do/to use something, which is a purely inner construct to your program logic.


Small addition: here is a small list of neat examples of [ab]using IDisposable: http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable.




回答2:


I think it's most helpful to think of IDisposable in terms of responsibilities. An object should implement IDisposable if it knows of something that will need to be done between the time it's no longer needed and the end of the universe (and preferably as soon as possible), and if it's the only object with both the information and impetus to do it. An object which opens a file, for example, would have a responsibility to see that the file gets closed. If the object were to simply disappear without closing the file, the file might not get closed in any reasonable timeframe.

It's important to note that even objects which only interact with 100% managed objects can do things that need to be cleaned up (and should use IDisposable). For example, an IEnumerator which attaches to a collection's "modified" event will need to detach itself when it is no longer needed. Otherwise, unless the enumerator uses some complex trickery, the enumerator will never be garbage-collected as long as the collection is in scope. If the collection is enumerated a million times, a million enumerators would get attached to its event handler.

Note that it's sometimes possible to use finalizers for cleanup in cases where, for whatever reason, an object gets abandoned without Dispose having been called first. Sometimes this works well; someitmes it works very badly. For example, even though Microsoft.VisualBasic.Collection uses a finalizer to detach enumerators from "modified" events, attempting to enumerate such an object thousands of times without an intervening Dispose or garbage-collection will cause it to get very slow--many orders of magnitude slower than the performance that would result if one used Dispose correctly.




回答3:


So, my question is, if you've got a type that doesn't contain unmanaged resources, is it worth implementing IDisposable?

When someone places an IDisposable interface on an object, this tells me that the creator intends on this either doing something in that method or, in the future they may intend to. I always call dispose in this instance just to be sure. Even if it doesn't do anything right now, it might in the future, and it sucks to get a memory leak because they updated an object, and you didn't call Dispose when you were writing code the first time.

In truth it's a judgement call. You don't want to over implement it, because at that point why bother having a garbage collector at all. Why not just manually dispose every object. If there is a possibility that you'll need to dispose unmanaged resources, then it might not be a bad idea. It all depends, if the only people using your object are the people on your team, you can always follow up with them later and say, "Hey this needs to use an unmanaged resource now. We have to go through the code and make sure we've tidied up." If you are publishing this for other organizations to use that's different. There is no easy way to tell everyone who might have implemented that object, "Hey you need to be sure this is now disposed." Let me tell you there are few things that make people madder than upgrading a third party assembly to find out that they are the ones who changed their code and made your application have run away memory problems.

My colleage replied that, under the covers, an ADO.NET connection is a managed resource. My reply to his reply was that everything ultimately is an unmanaged resource.

He's right, it's a managed resource right now. Will they ever change it? Who knows, but it doesn't hurt to call it. I don't try and make guesses as to what the ADO.NET team does, so if they put it in and it does nothing, that's fine. I'll still call it, because one line of code isn't going to affect my productivity.

You also run into another scenario. Let's say you return an ADO.NET connection from a method. You don't know that ADO connection is the base object or a derived type off the bat. You don't know if that IDisposable implementation has suddenly become necessary. I always call it no matter what, because tracking down memory leaks on a production server sucks when it's crashing every 4 hours.




回答4:


While there are good answers to this already, I just wanted to make something explicit.

There are three cases for implementing IDisposable:

  1. You are using unmanaged resources directly. This typically involves retrieving an IntPrt or some other form of handle from a P/Invoke call that has to be released by a different P/Invoke call
  2. You are using other IDisposable objects and need to be responsible for their disposition
  3. You have some other need of or use for it, including the convenience of the using block.

While I might be a bit biased, you should really read (and show your colleague) the StackOverflow Wiki on IDisposable.




回答5:


Dispose should be used for any resource with a limited lifetime. A finalizer should be used for any unmanaged resource. Any unmanaged resource should have a limited lifetime, but there are plenty of managed resources (like locks) that also have limited lifetimes.




回答6:


Note that unmanaged resources may well include standard CLR objects, for instance held in some static fields, all ran in safe mode with no unmanaged imports at all.

There is no simple way to tell if a given class implementing IDiposable actually needs to clean something. My rule of thumb is to always call Dispose on objects I don't know too well, like some 3rd party library.




回答7:


No, it's not only for unmanaged resources.

It's suggested like a basic cleanup built-in mechanism called by framework, that enables you possibility to cleanup whatever resource you want, but it's best fit is naturally unmanaged resources management.




回答8:


If you aggregate IDisposables then you should implement the interface in order that those members get cleaned up in a timely way. How else is myConn.Dispose() going to get called in the ADO.Net connection example you cite?

I don't think it's correct to say that everything is an unmanaged resource in this context though. Nor do I agree with your colleague.




回答9:


You are right. Managed database connections, files, registry keys, sockets etc. all hold on to unmanaged objects. That is why they implement IDisposable. If your type owns disposable objects you should implement IDisposable and dispose them in your Dispose method. Otherwise they may stay alive until garbage collected resulting in locked files and other unexpected behavior.




回答10:


everything ultimately is an unmanaged resource.

Not true. Everything except memory used by CLR objects which is managed (allocated and freed) only by the framework.

Implementing IDisposable and calling Dispose on an object that does not hold on to any unmanaged resources (directly or indirectly via dependent objects) is pointless. It does not make freeing that object deterministic because you can't directly free object's CLR memory on your own as it is always only GC that does that. Object being a reference type because value types, when used directly at a method level, are allocated/freed by stack operations.

Now, everyone claims to be right in their answers. Let me prove mine. According to documentation:

Object.Finalize Method allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.

In other words object's CLR memory is released just after Object.Finalize() is called. [note: it is possible to explicitly skip this call if needed]

Here is a disposable class with no unmanaged resources:

internal class Class1 : IDisposable
{
    public Class1()
    {
        Console.WriteLine("Construct");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose");
    }

    ~Class1()
    {
        Console.WriteLine("Destruct");
    }
}

Note that destructor implicitly calls every Finalize in the inheritance chain down to Object.Finalize()

And here is the Main method of a console app:

static void Main(string[] args)
{
    for (int i = 0; i < 10; i++)
    {
        Class1 obj = new Class1();
        obj.Dispose();
    }

    Console.ReadKey();
}

If calling Dispose was a way to free a managed object in a deterministic way, every "Dispose" would be immediately followed by a "Destruct", right? See for yourself what happens. It is most interesting to run this app from a command line window.

Note: There is a way to force GC to collect all objects which are pending finalization in the current app domain but no for a single specific object. Nevertheless you do not need to call Dispose to have an object in the finalization queue. It is strongly discouraged to force collection as it will likely hurt overall application performance.

EDIT

There is one exception - state management. Dispose can handle state change if your object happens to manage an outside state. Even if state is not an unmanaged object it is very convenient to use it like one because of special treatment IDisposable has. Example would be a security context or impersonation context.

using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate()))
{
    // do something as SomeUser
}

// back to your user

It is not the best example because WindowsImpersonationContext uses system handle internally but you get the picture.

Bottom line is that when implementing IDisposable you need to have (or plan to have) something meaningful to do in the Dispose method. Otherwise it's just a waste of time. IDisposable does not change how your object is managed by GC.




回答11:


Your Type should implement IDisposable if it references unmanaged resources or if it holds references to objects that implement IDisposable.




回答12:


In one of my projects I had a class with managed threads inside it, we'll call them thread A, and thread B, and an IDisposable object, we'll call it C.

A used to dispose of C on exiting. B used to use C to save exceptions.

My class had to implement IDisposable and a descrtuctor to ensure things are disposed of in the correct order. Yes the GC could clean up my items, but my experience was there was a race condition unless I managed the clean up of my class.




回答13:


Short Answer: Absolutely NOT. If your type has members that are managed or unmanaged, you should implement IDisposable.

Now details: I've answered this question and provided much more detail on the internals of memory management and the GC on questions here on StackOverflow. Here are just a few:

  • Is it bad practice to depend on the .NET automated garbage collector?
  • What happens if I don't call Dispose on the pen object?
  • Dispose, when is it called?

As far as best practices on the implementation of IDisposable, please refer to my blog post:

How do you properly implement the IDisposable pattern?




回答14:


Implement IDisposable if the object owns any unmanaged objects or any managed disposable objects

If an object uses unmanaged resources, it should implement IDisposable. The object that owns a disposable object should implement IDisposable to ensure that the underlying unmanaged resources are released. If the rule/convention is followed, it is therefore logical to conclude that not disposing managed disposable objects equals not freeing unmanaged resources.




回答15:


Not necessary resources at all (either managed or unmanaged). Often, IDisposable is just a convenient way to elimnate combersome try {..} finally {..}, just compare:

  Cursor savedCursor = Cursor.Current;

  try {
    Cursor.Current = Cursors.WaitCursor;

    SomeLongOperation();
  }
  finally {
    Cursor.Current = savedCursor;
  }

with

  using (new WaitCursor()) {
    SomeLongOperation();
  }

where WaitCursor is IDisposable to be suitable for 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;
      }
    }
  }

You can easily combine such classes:

  using (new WaitCursor()) {
    using (new RegisterServerLongOperation("My Long DB Operation")) {
      SomeLongRdbmsOperation();  
    }

    SomeLongOperation();
  }


来源:https://stackoverflow.com/questions/10316418/should-dispose-only-be-used-for-types-containing-unmanaged-resources

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!