Why is it always necessary to implement IDisposable on an object that has an IDisposable member?
Asked Answered
K

7

4

From what I can tell, it is an accepted rule that if you have a class A that has a member m that is IDisposable, A should implement IDisposable and it should call m.Dispose() inside of it.

I can't find a satisfying reason why this is the case.

I understand the rule that if you have unmanaged resources, you should provide a finalizer along with IDisposable so that if the user doesn't explicitly call Dispose, the finalizer will still clean up during GC.

However, with that rule in place, it seems like you shouldn't need to have the rule that this question is about. For instance...

If I have a class:

class MyImage{
  private Image _img;
  ... }

Conventions states that I should have MyImage : IDisposable. But if Image has followed conventions and implemented a finalizer and I don't care about the timely release of resources, what's the point?

UPDATE

Found a good discussion on what I was trying to get at here.

Knout answered 23/5, 2011 at 18:47 Comment(7)
Dispose allows you to release resources earlier, which is a good thing. A finalizer is for "last chance" resource deallocations.Flannel
For the same reasons you should implement and call .dispose() anywhere in the first place instead of relying on finalizers. If you can't get the MyImage you use to dispose the Image _img, you are in the same reason as if you had an Image and couldn't dispose it.Rendezvous
@delnan: I don't really understand what you are trying to say. It seems as if you are implying that not calling Dispose() on _img would be a very bad thing. I agree that it is wise to do that in situations where you want to free up resources, but my question is for situations where you don't care about the timely release of resources. ~Image() should take care of anything left behind.Knout
The point is that you should care. Or at least give people who do care the means of taking care of it (by exposing a way to dispose the Image). Not doing this seems like not checking whether malloc returned NULL in C because "I won't consume that much memory".Rendezvous
@delnan: "Not doing this seems like not checking whether malloc returned NULL in C". False. Image implements a finalizer which means eventually (possibly at the cost of performance) GC will cause it to clean itself up.Knout
Finalizers may never run. Just like malloc may never return NULL. In both cases, you'll be fine most of the time but that's no reason to abandon good practice especially since it costs so little to at provide the solution even if you don't use them yourself respectively to at least detect the problem when it occurs.Rendezvous
@delnan - That "Finalizers may never run" link is a life changer - thankyou.Jarad
B
6

But if Image has followed conventions and implemented a finalizer and I don't care about the timely release of resources, what's the point?

Then there isn't one, if you don't care about timely release, and you can ensure that the disposable object is written correct (in truth I never make an assumption like that, not even with MSs code. You never know when something accidentally slipped by). The point is that you should care, as you never know when it will cause a problem. Think about an open database connection. Leaving it hanging around, means that it isn't replaced in the pool. You can run out if you have several requests come in for one.

Nothing says you have to do it if you don't care. Think of it this way, it's like releasing variables in an unmanaged program. You don't have to, but it is highly advisable. If for no other reason the person inheriting from the program doesn't have to wonder why it wasn't taken care of and then try and clear it up.

Brouwer answered 23/5, 2011 at 18:52 Comment(6)
I specifically chose Image instead of any file streams or connections because of the 'timely' nature of those resources.Knout
I figured that is where you were going with it, but I wanted to generalize what i was trying to say.Brouwer
"it's like releasing variables in an unmanaged program". Is that actually a valid analogy? In an unmanaged program, wouldn't not releasing variables possibly lead to memory leaks?Knout
yeah, but it's kind of the same idea. You don't have to do it, if you don't care about reclaiming memory. It's not advisable but possible.Brouwer
Fair enough. I just wanted to make sure I wasn't missing something in my understanding of GC.Knout
You have to know what IDisposable does. Unless you do, you really should always call .Dispose(). While not a issue with an Image, consider an IDisposables that registers itself as an event handler of events from a a long lived object, and removes itself as a handler in its .Dispose() method. If you fail to call .Dispose when you're done, that object will not remove itself as an event handler. And then it will not be eliglble for garbage collection, and you effectivly have a memory leak - that memory will never be reclaimed until you exit your applicationTahoe
M
22

But if Image has followed conventions and implemented a finalizer and I don't care about the timely release of resources, what's the point?

You've missed the point of Dispose entirely. It's not about your convenience. It's about the convenience of other components that might want to use those unmanaged resources. Unless you can guarantee that no other code in the system cares about the timely release of resources, and the user doesn't care about timely release of resources, you should release your resources as soon as possible. That's the polite thing to do.

In the classic Prisoner's Dilemma, a lone defector in a world of cooperators gains a huge benefit. But in your case, being a lone defector produces only the tiny benefit of you personally saving a few minutes by writing low-quality, best-practice-ignoring code. It's your users and all the programs they use that suffer, and you gain practically nothing. Your code takes advantage of the fact that other programs unlock files and release mutexes and all that stuff. Be a good citizen and do the same for them. It's not hard to do, and it makes the whole software ecosystem better.

UPDATE: Here is an example of a real-world situation that my team is dealing with right now.

We have a test utility. It has a "handle leak" in that a bunch of unmanaged resources aren't aggressively disposed; it's leaking maybe half a dozen handles per "task". It maintains a list of "tasks to do" when it discovers disabled tests, and so on. We have ten or twenty thousand tasks in this list, so we very quickly end up with so many outstanding handles -- handles that should be dead and released back into the operating system -- that soon none of the code in the system that is not related to testing can run. The test code doesn't care. It works just fine. But eventually the code being tested can't make message boxes or other UI and the entire system either hangs or crashes.

The garbage collector has no reason to know that it needs to run finalizers more aggressively to release those handles sooner; why should it? Its job is to manage memory. Your job is to manage handles, so you've got to do that job.

Melodrama answered 23/5, 2011 at 19:35 Comment(5)
I don't think I've missed the point of Dispose. I'm just assuming for this question "that no other code in the system cares about the timely release of resources, and the user doesn't care about timely release of resources". This isn't an attempt to ask everyone else what the hell they're doing with all this Dispose stuff. It is more of an attempt to ensure that I completely understand how .NET garbage collection works.Knout
@mphair: Then I don't understand the point of the question. You're basically asking "if no one cares about code quality, then does anyone care about code quality?" The question answers itself. If no one cares about performance of disposed resources, then should you care about performance of disposed resources? I guess not, but answering a tautological question about a counterfactual situation doesn't give anyone any new knowledge that they can apply to realistic scenarios.Melodrama
The situation is not necessarily counterfactual and is by no means tautological. How about this. Imagine a prison where every once in a while a prisoner runs for it. There is a dude standing at the one exit with a big net. There is also a dude in a tower with a rifle backing up the guy with the net. The cost of not netting the escapee is the cost of a bullet and the loss of a prisoner (it is ideal to catch him with the net). But do you absolutely need to catch him with the net to prevent him from escaping?Knout
@mphair: Again, you are misunderstanding the purpose of disposing. Again, the purpose is not to "prevent the prisoner from escaping". The purpose of disposing your resources early is so that other code that needs those resources can get it. I'll update my answer with an example.Melodrama
I appreciate the need to push standards and have said in other responses that I'm not trying to convert everyone out of practicing those standards. However, without knowing all possible situations in which this question might validly come up, it seems odd to pull out the pedestal and start pissing on the peons.Knout
B
6

But if Image has followed conventions and implemented a finalizer and I don't care about the timely release of resources, what's the point?

Then there isn't one, if you don't care about timely release, and you can ensure that the disposable object is written correct (in truth I never make an assumption like that, not even with MSs code. You never know when something accidentally slipped by). The point is that you should care, as you never know when it will cause a problem. Think about an open database connection. Leaving it hanging around, means that it isn't replaced in the pool. You can run out if you have several requests come in for one.

Nothing says you have to do it if you don't care. Think of it this way, it's like releasing variables in an unmanaged program. You don't have to, but it is highly advisable. If for no other reason the person inheriting from the program doesn't have to wonder why it wasn't taken care of and then try and clear it up.

Brouwer answered 23/5, 2011 at 18:52 Comment(6)
I specifically chose Image instead of any file streams or connections because of the 'timely' nature of those resources.Knout
I figured that is where you were going with it, but I wanted to generalize what i was trying to say.Brouwer
"it's like releasing variables in an unmanaged program". Is that actually a valid analogy? In an unmanaged program, wouldn't not releasing variables possibly lead to memory leaks?Knout
yeah, but it's kind of the same idea. You don't have to do it, if you don't care about reclaiming memory. It's not advisable but possible.Brouwer
Fair enough. I just wanted to make sure I wasn't missing something in my understanding of GC.Knout
You have to know what IDisposable does. Unless you do, you really should always call .Dispose(). While not a issue with an Image, consider an IDisposables that registers itself as an event handler of events from a a long lived object, and removes itself as a handler in its .Dispose() method. If you fail to call .Dispose when you're done, that object will not remove itself as an event handler. And then it will not be eliglble for garbage collection, and you effectivly have a memory leak - that memory will never be reclaimed until you exit your applicationTahoe
L
2

Firstly, there's no guaranteeing when an object will be cleaned up by the finalizer thread - think about the case where a class has a reference to a sql connection. Unless you make sure this is disposed of promptly, you'll have a connection open for an unknown period of time - and you won't be able to reuse it.

Secondly, finalization is not a cheap process - you should be making sure that if your objects are disposed of properly you're calling GC.SuppressFinalize(this) to prevent finalization happening.

Expanding on the "not cheap" aspect, the finalizer thread is a high-priority thread. It will take resources away from your main application if you give it too much to do.

Edit: Ok, here's a blog article by Chris Brummie about Finalization, including why it is expensive. (I knew I'd read loads about this somewhere)

League answered 23/5, 2011 at 18:57 Comment(0)
Q
1

If you don't care about the timely release of resources, then indeed there is no point. If you can be sure that the code is only for your consumption and you've got plenty of free memory/resources why not let GC hoover it up when it chooses to. OTOH, if someone else is using your code and creating many instances of (e.g.) MyImage, it's going to be pretty difficult to control memory/resource usage unless it disposes nicely.

Qp answered 23/5, 2011 at 18:53 Comment(0)
T
1

Many classes require that Dispose be called to ensure correctness. If some C# code uses an iterator with a "finally" block, for example, the code in that block will not run if an enumerator is created with that iterator and not disposed. While there a few cases where it would be impractical to ensure objects were cleaned up without finalizers, for the most part code which relies upon finalizers for correct operation or to avoid memory leaks is bad code.

If your code acquires ownership of an IDisposable object, then unless either the object's cleass is sealed or your code creates the object by calling a constructor (as opposed to a factory method) you have no way of knowing what the real type of the object is, and whether it can be safely abandoned. Microsoft may have originally intended that it should be safe to abandon any type of object, but that is unrealistic, and the belief that it should be safe to abandon any type of object is unhelpful. If an object subscribes to events, allowing for safe abandonment will require either adding a level of weak indirection to all events, or a level of (non-weak) indirection to all other accesses. In many cases, it's better to require that a caller Dispose an object correctly than to add significant overhead and complexity to allow for abandonment.

Note also, btw, that even when objects try to accommodate abandonment it can still be very expensive. Create a Microsoft.VisualBasic.Collection (or whatever it's called), add a few objects, and create and Dispose a million enumerators. No problem--executes very quickly. Now create and abandon a million enumeartors. Major snooze fest unless you force a GC every few thousand enumerators. The Collection object is written to allow for abandonment, but that doesn't mean it doesn't have a major cost.

Testudinal answered 24/5, 2011 at 15:30 Comment(0)
F
1

If an object you're using implements IDisposable, it's telling you it has something important to do when you're finished with it. That important thing may be to release unmanaged resources, or unhook from events so that it doesn't handle events after you think you're done with it, etc, etc. By not calling the Dispose, you're saying that you know better about how that object operates than the original author. In some tiny edge cases, this may actually be true, if you authored the IDisposable class yourself, or you know of a bug or performance problem related to calling Dispose. In general, it's very unlikely that ignoring a class requesting you to dispose it when you're done is a good idea.

Talking about finalizers - as has been pointed out, they have a cost, which can be avoided by Disposing the object (if it uses SuppressFinalize). Not just the cost of running the finalizer itself, and not just the cost of having to wait till that finalizer is done before the GC can collect the object. An object with a finalizer survives the collection in which it is identified as being unused and needing finalization. So it will be promoted (if it's not already in gen 2). This has several knock on effects:

  • The next higher generation will be collected less frequently, so after the finalizer runs, you may be waiting a long time before the GC comes around to that generation and sweeps your object away. So it can take a lot longer to free memory.
  • This adds unnecessary pressure to the collection the object is promoted to. If it's promoted from gen 0 to gen 1, then now gen 1 will fill up earlier than it needs to.
  • This can lead to more frequent garbage collections at higher generations, which is another performance hit.
  • If the object's finalizer isn't completed by the time the GC comes around to the higher generation, the object can be promoted again. Hence in a bad case you can cause an object to be promoted from gen 0 to gen 2 without good reason.

Obviously if you're only doing this on one object it's not likely to cost you anything noticeable. If you're doing it as general practice because you find calling Dispose on objects you're using tiresome, then it can lead to all of the problems above.

Dispose is like a lock on a front door. It's probably there for a reason, and if you're leaving the building, you should probably lock the door. If it wasn't a good idea to lock it, there wouldn't be a lock.

Flew answered 26/5, 2011 at 20:25 Comment(0)
I
0

Even if you don't care in this particular case, you should still follow the standard because you will care in some cases. It's much easier to set a standard and follow it always based on specific guidelines than have a standard that you sometimes disregard. This is especially true as your team grows and your product ages.

Indonesia answered 23/5, 2011 at 19:1 Comment(2)
The main reason why I question the standard in this case is because this seems like it would lead to an explosion in IDisposable (I haven't decided if that is even a bad thing yet). If every class that has a member that is IDisposable needs to be IDisposable, it will have the tendency to cause more and more of the population of objects to become IDisposable. If everything is tagged a certain way, the tag becomes less and less meaningful.Knout
@mphair, if everything was IDisposable, then it's easy to be consistent.Indonesia

© 2022 - 2024 — McMap. All rights reserved.