.NET Forum / Languages / C# / March 2008
Can I have back my destructors, please?
|
|
Thread rating:  |
cctv.star@gmail.com - 21 Feb 2008 01:42 GMT In C++ you have RAII idiom that takes care of all your resources - memory, file handles, etc.
In C# you have GC that takes care of the memory, but it looks like you're completely on you own as far as other resources are concerned - just miss this Dispose() call and wait for all kind of interesting things to happen.
For every class that you consume the first thing you should note is whether it implements IDisposable or not, and handle it accordingly.
Now the problem: suppose you use some class, which does not implement IDisposable. All is well. And then, the maintainer of this class decides that he needs to use some other object, which does implement IDisposable, and to make this object a member. Although this should be just an implementation detail and completely private business, he has to make his own class implement IDisposable. And now your code still compiles fine, but is no longer correct, although the only thing that really has changed is just implementation of a class you use.
In a sense, whether some class implements IDisposable or not is just an implementation artifact for the author, but is indeed an interface change for the consumer.
Actually, I'm still learning C#, so I hope something of what I've written above is incorrect. Otherwise, please advise how to deal with the problem I've mentioned in practice (is there any way, at least, to make the consumer code uncompilable after such change?).
Arne Vajhøj - 21 Feb 2008 02:15 GMT > In C++ you have RAII idiom that takes care of all your resources - > memory, file handles, etc. [quoted text clipped - 25 lines] > the problem I've mentioned in practice (is there any way, at least, to > make the consumer code uncompilable after such change?). Question in subject line: no.
Last question: not really. The best direction would be to layer the code, so that each layer expose a clean interface with no such requirements. The problem happen because the two classes you consider independent of each other are in fact tightly coupled.
Arne
Scott Roberts - 21 Feb 2008 03:28 GMT >> In C++ you have RAII idiom that takes care of all your resources - >> memory, file handles, etc. [quoted text clipped - 35 lines] > > Arne I also have trouble with IDisposable. Not the concept, just with remember if/when I need to "dispose"or not.
Could you expand on your suggestion to "layer the code"? For example, I don't feel that using the SqlConnection class from within my class necessarily makes my class "tightly coupled" with the SqlConnection class. However, my code does need to know whether SqlConnection implements IDisposable or not and act accordingly. How could I "layer" my code to remove this "coupling"? Or am I misunderstanding you completely?
Marc Gravell - 21 Feb 2008 05:12 GMT > but it looks like > you're completely on you own as far as other resources are concerned You still have the finalizer, which gets invoked as GC destroys an object... but this should only be used as a safety net (and ideally would whinge: ideally IDisposable should be used)
> For example, I > don't feel that using the SqlConnection class from within my class > necessarily makes my class "tightly coupled" with the SqlConnection class. If you mean "within some method(s), obtains, uses and releases a SqlConnection" then I'd agree; if you mean "has a SqlConnection as an instance-field that only got created because of this instance [perhaps in the constructor]", then I'd argue that this very much *does* couple them, and your class should implement IDisposable to hopefull clean up the connection ASAP. Of course, perhaps a better pattern here would be to not have a SqlConnection as a field, and let the connection-pool do its intended job - but that is very example specific.
> if/when I need to "dispose"or not. True, true; well a good rule of thumb is to (at least) check for a public Dispose() method, and wrap with "using". But the IDE could help out here! I can't remember, but maybe FxCop has a rule on this?
Marc
KWienhold - 21 Feb 2008 07:56 GMT > > if/when I need to "dispose"or not. > [quoted text clipped - 3 lines] > > Marc I agree, I have always wondered why VS doesn't tell me that I have created an instance of a class that implements IDisposable, but forgot to call Dispose() on it. However that wouldn't really help if (like the OP mentions) a class you are already using decides to implement IDisposable without you knowing about it. Then again, implementing IDisposable is definately a breaking change, so it should not be made once an interface has become public, so hopefully this should not happen.
Kevin Wienhold
Peter Duniho - 21 Feb 2008 18:54 GMT >> > if/when I need to "dispose"or not. >> [quoted text clipped - 7 lines] > created an instance of a class that implements IDisposable, but forgot > to call Dispose() on it. Well, to be fair, it's not an easy problem to solve. Since Dispose() can legitimately be called anywhere, not just in the local scope where the object is instantiated, or even within the same assembly for that matter, there could be a lot of false positives on such a warning.
But I do agree (and have said so here before :) ) that _something_ ought to be done somewhere to advertise better in the IDE that you're using an object that implements IDisposable and thus needs disposing at some point.
I just don't know off the top of my head what the best way to do that would be. :)
> However that wouldn't really help if (like the OP mentions) a class > you are already using decides to implement IDisposable without you > knowing about it. > Then again, implementing IDisposable is definately a breaking change, > so it should not be made once an interface has become public, so > hopefully this should not happen. Yes, I wouldn't worry about that case too much. It'd be a very bad idea to add IDisposable to an object after the fact without going back and revisiting every single use of that object.
Pete
JS - 05 Mar 2008 19:03 GMT > I just don't know off the top of my head what the best way to do that > would be. :) At the very least, it should be more obvious when you're working with an IDisposable object. Maybe the editor could highlight IDisposable types with a different color, or intellisense could somehow show you that the object is IDisposable.
cctv.star@gmail.com - 21 Feb 2008 20:08 GMT > Then again, implementing IDisposable is definately a breaking change, > so it should not be made once an interface has become public, so > hopefully this should not happen. The problem is, implementing IDisposable could be caused by implementation needs.
I can't help thinking about IDisposable as having very odd semantics, different to that of other, "normal" interfaces - it doesn't provide any service to a client, but rather makes the client responsible (for calling Dispose()). And since necessity for cleanup is an implementation artifact rather than a real public interface change, I can easily imagine it coming during later stages of development.
Jeroen Mostert - 21 Feb 2008 20:25 GMT >> Then again, implementing IDisposable is definately a breaking change, >> so it should not be made once an interface has become public, so [quoted text clipped - 8 lines] > calling Dispose()). And since necessity for cleanup is an > implementation artifact rather than a real public interface change, See, that's where you're wrong, because you're used to C++. Because *all* finalization is deterministic in C++, it does reduce to an implementation artifact there. But exactly because .NET has nondeterministic finalization, giving the class a need for deterministic finalization is a breaking change. The public interface remains the same, but the semantics are different.
It's not merely "an implementation artifact", because it makes a tangible difference to the users of the class: if they don't Dispose() explicitly, there's a chance for resource leaks. Transient resource leaks, granted, but that's still enough to cripple your application. That sort of "artifact" deserves all the attention it can get.
> I can easily imagine it coming during later stages of development. Yes, and that's unfortunate. On the other hand, it's not so common for a class to "suddenly" start owning precious resources that require disposing without anyone ever foreseeing the need for this. Especially not if those resources are claimed during the object's entire lifetime. If it can deterministically dispose of a field sooner than that, it should definitely do so.
 Signature J.
cctv.star@gmail.com - 21 Feb 2008 23:58 GMT > See, that's where you're wrong, because you're used to C++. Because *all* > finalization is deterministic in C++, it does reduce to an implementation > artifact there. But exactly because .NET has nondeterministic finalization, > giving the class a need for deterministic finalization is a breaking change. > The public interface remains the same, but the semantics are different. Yes, that I think was the root of my confusion.
Thanks for the good explanation!
Ollie - 21 Feb 2008 11:07 GMT > In C++ you have RAII idiom that takes care of all your resources - > memory, file handles, etc. [quoted text clipped - 25 lines] > the problem I've mentioned in practice (is there any way, at least, to > make the consumer code uncompilable after such change?). If a class you are using is modified so that it now implements the IDisposable interface and it has the complete Dispose pattern implemented correctly it will NOT break your code if you aren't calling Dispose on the modified class - it just means the use of the modified class by you is sub optiminal from a resource (GC) perspective, the GC will call the finalizer added to the modified class which should call the 'Dispose' method. You jsut won't know when this will be as the modified class is not being used in a 'using' block or having the 'Dispose' method called directly.
HTH
Ollie
Jon Skeet [C# MVP] - 21 Feb 2008 11:22 GMT <snip>
> If a class you are using is modified so that it now implements the > IDisposable interface and it has the complete Dispose pattern implemented > correctly it will NOT break your code if you aren't calling Dispose on the > modified class - it just means the use of the modified class by you is sub > optiminal from a resource (GC) perspective, the GC will call the finalizer > added to the modified class which should call the 'Dispose' method. However, whether or not waiting for the finalizer thread to kick in breaks your code depends on what it's hanging on to. If it's hanging on to a database connection, for instance, it could easily break your code - you might have as many of these objects as there are poolable connections, for instance.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet World class .NET training in the UK: http://iterativetraining.co.uk
Ollie - 05 Mar 2008 14:23 GMT > However, whether or not waiting for the finalizer thread to kick in > breaks your code depends on what it's hanging on to. If it's hanging on > to a database connection, for instance, it could easily break your code > - you might have as many of these objects as there are poolable > connections, for instance. Surely the code in the example you quote would have this problem anyway?
Ollie
Jon Skeet [C# MVP] - 05 Mar 2008 14:37 GMT > > However, whether or not waiting for the finalizer thread to kick in > > breaks your code depends on what it's hanging on to. If it's hanging on [quoted text clipped - 3 lines] > > Surely the code in the example you quote would have this problem anyway? Which code? I didn't quote any example code. (I didn't see any code in any post in this thread, in fact.)
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet World class .NET training in the UK: http://iterativetraining.co.uk
Ollie Riches - 05 Mar 2008 17:38 GMT > > > However, whether or not waiting for the finalizer thread to kick in > > > breaks your code depends on what it's hanging on to. If it's hanging on [quoted text clipped - 10 lines] > Jon Skeet - <sk...@pobox.com>http://www.pobox.com/~skeet Blog:http://www.msmvps.com/jon.skeet > World class .NET training in the UK:http://iterativetraining.co.uk Sorry I meant scenario 'if it's hanging on to a database connection' because even without the class implementing the dispose pattern the database (handle) with still be finalized
Ollie Riches
Jon Skeet [C# MVP] - 05 Mar 2008 18:04 GMT > > > > However, whether or not waiting for the finalizer thread to kick in > > > > breaks your code depends on what it's hanging on to. If it's hanging on [quoted text clipped - 10 lines] > because even without the class implementing the dispose pattern the > database (handle) with still be finalized Yes - but the point is that introducing this situation is effectively a breaking change. With deterministic destructors, it wouldn't be. I'm not saying deterministic destructors are feasible without other problems, but I'm putting it in the context of the original post.
Implementing IDisposable when it wasn't implemented before is putting an additional responsibility on the caller, without them being aware of it.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet World class .NET training in the UK: http://iterativetraining.co.uk
Brian Gideon - 05 Mar 2008 18:25 GMT > Now the problem: suppose you use some class, which does not implement > IDisposable. All is well. And then, the maintainer of this class [quoted text clipped - 5 lines] > although the only thing that really has changed is just implementation > of a class you use. Adding IDisposable to a class is a version breaking change. It may not be a binary breaking change which would mean your code still compiles, but it is a sematic breaking change since the behavior of the class changes. The documentation even mentions this...somewhere.
The only option the maintainer has is to create a brand new class that implements IDisposable. That way the original class remains untouched and backward compatible with existing code.
By the way, adding an interface after the fact can be a binary breaking change if it were added to an abstract base class. Subclasses would have to define their own implementation or else things won't compile.
Brian Gideon - 05 Mar 2008 18:31 GMT > By the way, adding an interface after the fact can be a binary > breaking change if it were added to an abstract base class. > Subclasses would have to define their own implementation or else > things won't compile. Err...you could provide a default implementation. What I meant to say was adding a method to an interface can be a binary breaking change. And of course it wouldn't matter if it were implemented by a concrete or abstract class. I was mixing different concepts in my mind and...aww...nevermind...I obviously got that point completely wrong :)
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|