IDisposable, Finalizers and the definition of an unmanaged resource
Asked Answered
D

8

5

I'm trying to make sure that my understanding of IDisposable is correct and there's something I'm still not quite sure on.

IDisposable seems to serve two purpose.

  1. To provide a convention to "shut down" a managed object on demand.
  2. To provide a convention to free "unmanaged resources" held by a managed object.

My confusion comes from identifying which scenarios have "unmanaged resources" in play.

Say you are using a Microsoft-supplied IDisposable-implementing (managed) class (say, database or socket-related).

  1. How do you know whether it is implementing IDisposable for just 1 or 1&2 above?
  2. Are you responsible for making sure that unmanaged resources it may or may not hold internally are freed? Should you be adding a finalizer (would that be the right mechanism?) to your own class that calls instanceOfMsSuppliedClass.Dispose()?
Donau answered 18/6, 2009 at 15:15 Comment(0)
P
10
  1. How do you know whether it is implementing IDisposable for just 1 or 1&2 above?

The answer to your first question is "you shouldn't need to know". If you're using third party code, then you are its mercy to some point - you'd have to trust that it's disposing of itself properly when you call Dispose on it. If you're unsure or you think there's a bug, you could always try using Reflector() to disassemble it (if possible) and check out what it's doing.

  1. Am I responsible for making sure that unmanaged resources it may or may not hold internally are freed? Should I be adding a finalizer (would that be the right mechanism?) to my own class that calls instanceOfMsSuppliedClass.Dispose()?

You should rarely, if ever, need to implement a finalizer for your classes if you're using .Net 2.0 or above. Finalizers add overhead to your class, and usually provide no more functionality then you'd need with just implementing Dispose. I would highly recommend visiting this article for a good overview on disposing properly. In your case, you would want to call instanceofMSSuppliedClass.Dispose() in your own Dispose() method.

Ultimately, calling Dispose() on an object is good practice, because it explictly lets the GC know that you're done with the resource and allows the user the ability to clean it up immediately, and indirectly documents the code by letting other programmers know that the object is done with the resources at that point. But even if you forget to call it explicitly, it will happen eventually when the object is unrooted (.Net is a managed platform after all). Finalizers should only be implemented if your object has unmanaged resources that will need implicit cleanup (i.e. there is a chance the consumer can forget to clean it up, and that this will be problematic).

Prochora answered 18/6, 2009 at 15:22 Comment(3)
"Ultimately, calling Dispose() .. allows the user the ability to clean it up immediately ... But even if you forget, it will happen eventually when the object is unrooted." That's the info I was really after. So, if the author of the class that's holding the ACTUAL unmanaged resources implements the IDisposable+Finalizer pattern properly, the cleanup will always happen some way or another in the end.Donau
Correct! And as a side note, if you ever did have to write a finalizer, you should only ever be finalizing objects that your object owns directly. Finalizing other object's resources can lead to all kinds of nasty.Prochora
That linked article calling Dispose a destructor syntax... dunno... I prefer this one: nitoprograms.blogspot.com/2009/08/… and the dirty details in codeproject.com/KB/dotnet/idisposable.aspxStilbestrol
W
5

You should always call Dispose on objects that implement IDisposable (unless they specifically tell you' it's a helpful convention, like ASP.NET MVC's HtmlHelper.BeginForm). You can use the "using" statement to make this easy. If you hang on to a reference of an IDisposable in your class as a member field then you should implement IDisposable using the Disposable Pattern to clean up those members. If you run a static-analysis tool like FxCop it will tell you the same.

You shouldn't be trying to second-guess the interface. Today that class might not use an unmanaged resource but what about the next version?

Watterson answered 18/6, 2009 at 15:21 Comment(1)
I always use FXCop to check I have called Dispose, and nearly always do it with a using statement.Belittle
K
1

You're not responsible for the contents of an object. Dispose() should be transparent, and free what it needs to free. After that, you're not responsible for it.

Unmanaged resources are resources like you would create in (managed) C++, where you allocate memory through pointers and "new" statements, rather than "gcnew" statements. When you're creating a class in C++, you're responsible for deleting this memory, since it is native memory, or unmanaged, and the garbage collector does not about it. You can also create this unmanaged memory through Marshal allocations, and, i'd assume, unsafe code.

When using Managed C++, you don't have to manually implement the IDisposable class either. When you write your deconstructor, it will be compiled to a Dispose() function.

Kathie answered 18/6, 2009 at 15:23 Comment(0)
V
1

If the class in question is Microsoft-supplied (ie.. database, etc..) then the handling of Dispose (from IDisposable) will most likely already be taken care of, it is just up to you to call it. For instance, standard practice using a database would look like:

//...
using (IDataReader dataRead = new DataReaderObject())
{
   //Call database
}

This is essentially the same as writing:

IDataReader dataRead = null;
try
{
    dataRead = new DataReaderObject()
    //Call database
}
finally
{
    if(dataRead != null)
    {
        dataRead.Dispose();
    }
}

From what I understand, it is generally good practice for you use the former on objects that inherit from IDisposable since it will ensure proper freeing of resources.

As for using IDisposable yourself, the implementation is up to you. Once you inherit from it you should ensure the method contains the code needed to dispose of any DB connections you may have manually created, freeing of resources that may remain or prevent the object from being destroyed, or just cleaning up large resource pools (like images). This also includes unmanaged resources, for instance, code marked inside an "unsafe" block is essentially unmanaged code that can allow for direct memory manipulation, something that definitely needs cleaning up.

Ventura answered 18/6, 2009 at 15:35 Comment(3)
Well I also didn't scope my IDataReader object correctly either, it was kinda meant as a quick description of the "using" statement. I will edit my post so as not to confuse people, or worse, risk having them write something that way, thanks!Ventura
Still won't compile; see my notes on VVS's answer. You should be instantiating immediately before the start of the "try" block. Or use "using".Orifice
The point of the code is a generalized description of what the using statement is doing. The code isn't meant to be utilized, mainly because using is the accepted method. But you are right, the code won't compile, the dataRead object should be set as NULL before it is initialized. There is a reasoning behind initializing the object inside the try statement though. The using statement will wrap the object in a try, because the object may have a complex initialization that throws an exception. The using statement would handle these and finish with a pass to finally.Ventura
E
1

The term "unmanaged resource" is something of a misnomer. The essential concept is that of a paired action--performing one action creates a need to perform some cleanup action. Opening a file creates a need to close it. Dialing a modem creates a need to hang up. A system may survive a failure to perform a cleanup operation, but the consequences may be severe.

When an object is said to "hold unmanaged resources", what is really meant is that the object has the information and impetus necessary to perform some required cleanup operation on some other entity, and there's no particular reason to believe that information and impetus exists anywhere else. If the only object with such information and impetus is completely abandoned, the required cleanup operation will never occur. The purpose of .Dispose is to force an object to perform any required cleanup, so that it may be safely abandoned.

To provide some protection against code which abandons objects without first calling Dispose, the system allows classes to register for "finalization". If an object of a registered class is abandoned, the system will give the object a chance to perform cleanup operation on other entities before it is abandoned for good. There's no guarantee as to how quickly the system will notice that an object has been abandoned, however, and various circumstances may prevent an object from being offered its chance to clean up. The term "managed resource" is sometimes used to refer to an object which will have to perform some cleanup before it's abandoned, but which will automatically register and attempt to perform such cleanup if someone fails to call Dispose.

Electroplate answered 20/2, 2011 at 21:30 Comment(10)
-1: "Unmanaged resource" is not a misnomer. It has a very clear meaning, and it's not the one you've just stated. Do you not know what "managed" means in the context of .NET?Hypotaxis
@John Saunders: Managed code is code run under the CLR, which uses objects that are managed by the CLR garbage collector. Unmanaged code, also called "native code", is code which is not run under the CLR. Because the CLR doesn't know anything about things that happen outside it, file handles, GDI handles, API-allocated memory blocks, etc. are all "native resources"; native resources are also "unmanaged resources", though the reverse is not necessarily true...Electroplate
@John Saunders: Although the CLR knows a lot about what goes on underneath it, it does not know all the important relationships that can exist between objects. It is entirely possible--and hardly unusual--for a short-lived object to make changes to a long-lived object which will cause trouble if they are not cleaned up. Two common examples are event subscriptions and locks. If an object with a short useful life subscribes to an event from a long-lived object, and is subsequently abandoned, the subscriber will be kept uselessly alive by the long-lived object.Electroplate
@John Saunders: If an object (e.g. an iterator) acquires a lock on another object and is then abandoned, the lock (and anything to which it controls access) may be unusable from that point forward. The GC will be able to clean things up once the event publisher or the object containing the lock goes out of scope, but there's no guarantee of when that will happen. The key issue in both cases is that there is an important type of relationship between objects that the CLR doesn't know about, and thus important cleanup is required which the GC will not perform.Electroplate
@John Saunders: If such relationships don't constitute "unmanaged resources", what are they?Electroplate
@supercat: the term "unmanaged resource" means the file handles, GDI handles, etc. That's all it means. Feel free to define your own term to mean something else, but please don't try to change the meaning of the term "unmanaged resource".Hypotaxis
@John Saunders: What term would you use for all the other things that require the use of IDisposable, and how would you recommend changing all the writings that claim it's only necessary for "managed or unmanaged resources"?Electroplate
@supercat: I have found no need for such a term. I also don't see any writings that say "managed or unmanaged". Can you post a link to one? I think you may be misreading it.Hypotaxis
@John Saunders: I don't have any reference to the exact phrase "managed or unmanaged resources", though many pages talk about managed resources and unmanaged resources. I would, however, note that msdn.microsoft.com/en-us/library/0s71x931.aspx says "The most common type of unmanaged resource is an object that wraps an operating system resource, such as a file, window, or network connection.", implying that there are other types. Note also that it's possible for 'shares' of a managed resource to behave as unmanaged resources from a GC perspective...Electroplate
@John Saunders: If a resource in managed code is shared by fifty objects, the GC may be able to discard the resource entirely if all fifty objects that use it go out of scope, but if 49 go out of scope the GC won't have any clue how to make the 49 pieces that are no longer used available for future allocation requests. The normal solution is for the class that holds each "share" to implement a finalizer, to notify the object when the share is no longer needed.Electroplate
N
0

Yes, you're responsible to call the Dispose method - or better use using statement. If an object is implementing IDisposable, you should always dispose it, no matter what.

using (var myObj = new Whatever())
{
   // ..
}

is similar to

{
  var myObj;
  try
  {
     myObj = new Whatever();
     // ..
  } 
  finally
  {
    if (myObj != null)
    {
      ((IDisposable)myObj).Dispose();
    }
  }
} // object scope ends here

EDIT: Added try/finally thanks to Talljoe - wow, that's complicated to get it right :)

EDIT2: I am not saying that you should use the second variant. I just wanted to show that "using" is nice syntactic sugar for a bunch of code that can get quite messy and hard to get right.

Nickienicklaus answered 18/6, 2009 at 15:21 Comment(6)
Actually, it's similar to: Whatever myObj; try { myObj = new Whatever(); /* .. */ } finally { if(myObj != null) ((IDisposable)myObj).Dispose;Watterson
@Talljoe- that's right a lot of people forget the myObj != null check.Belittle
Is Dispose() ALWAYS required? I always thought in the case of things like a FileStream, doing a FileStream.Close() and allowing the garbage collector to collect it wouldn't result in any issues. Dispose/Using would be better, i'd agree, but wouldn't this still be okay?Kathie
@Guard: Maybe, maybe not. And even if it's ok now, the implementation might change in future versions of the framework. If you ensure that you always dispose of any IDisposable objects then you don't need to worry about their underlying implementation, or any future changes to it.Proletarian
This code won't compile, because when you get to the "finally", myObj might be uninitialized. E.g., if "new Whatever()" threw an exception, then myObj never would have been assigned. Typically you would put the assignment just before the start of the "try" block. Then you don't need the null check. Alternatively, you could choose not to reinvent the wheel, and just use the "using" keyword -- that's what it's there for.Orifice
@Joe: Actually my point IS that you should use the "using" statement. I tried to show what "using" is internally and not how to do it the hard way.Nickienicklaus
T
0

Why should it matter to you?

When possible I wrap the scope of a disposable object in a using. This calls dispose at the end of the using. When not I explicitly call dispose whenever I no longer need the object.

Whether for reason 1 or reason 2 is not necessary.

Torrey answered 18/6, 2009 at 15:24 Comment(0)
L
-1

One piece that is missing here is the finalizer - my habit has been if I implement IDisposable I also have a finalizer to call Dispose() just in case my client doesn't. Yes, it adds overhead but if Dispose() IS called than the GC.SuppressFinalize(this) call eliminates it.

Luhey answered 19/6, 2009 at 13:12 Comment(6)
What does your Dispose do? If all you're doing is calling Dispose on other objects, then adding your own finalizer is a waste of effort and runtime. The objects that actually hold onto unmanaged resources (sockets, file handles, GDI+ handles, window handles, etc.) will almost certainly have finalizers already, and will clean themselves up if they're GCed without being Disposed first. You don't need to add extra overhead (and code complexity) to do what would have happened anyway.Orifice
MSDN is pretty explicit on the IDisposable pattern, to include a finalizer. I don't think 'almost certainly' is good enough, so I think a finalizer with IDisposable is important.Luhey
This is the discussion my awkwardly worded question was trying to get at. The distinction between IDisposable classes that ACTUALLY hold unmanaged resources, and IDisposable classes that just have other IDisposable instances as members.Donau
Call me dense, but I just don't see the difference. If I write a class that uses disposable objects, I'm going to implement IDisposable, with a finalizer, to make sure it's cleaned up. I don't think I should be thinking about whether the objects I'm using 'really' dispose something or not, just like MY callers shouldn't be making that determination about my class. IDisposable is a contract and its meaning is clear.Luhey
@n8wrl: well you should be thinking about that unfortunately. You should NOT call other classes' Dispose when disposing via the finalizer. Look at the MS patter carefully - if you implement the pattern properly then your class will do NOTHING when Dispose gets called from the finalizer, because of that "if (disposing)" thing.Stilbestrol
@n8wrl: The .net pattern doesn't include a finalizer; it includes some hooks to allow for the addition of a finalizer either in the present class or a derived class. I seriously dislike the Microsoft pattern, since it implies that one should allow an inherited class to add a finalizer, which in turn implies there might be a reason for an inherited class to do so. If an inherited class has some action that will need to be performed if it's abandoned, that behavior should be encapsulated in some other class whose purpose is to perform that behavior.Electroplate

© 2022 - 2024 — McMap. All rights reserved.