misha256
misha256

Reputation: 347

Does .NET have a better kind of IDisposable that the GC understands?

I have implemented IDisposable in a class. The Dispose method updates rows in a database from in-use to available. I like this non-standard use of IDisposable because you can use Using. But...

Does .NET have something like a "Super IDisposable", where the GC guarantees to call your cleanup method just in case you forgot, BUT where it's still safe to use the class' managed objects?

Upvotes: 2

Views: 116

Answers (4)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 727137

No, there is no such "Super IDisposable" interface.

When someone forgets to call Dispose (presumably, because they forgot to enclose the object in using) this is a programming error (as opposed to a runtime error). The best you can do in this situation is log as much information as possible to let programmers know what happened, so that they could identify and fix the programming error as soon as possible.

You implement this with a bool flag that you set to true inside the Dispose method, and then check inside the finalizer. If the finalizer runs with the flag being set to false, you know that there has been no call of Dispose, so you log an error.

Upvotes: 1

HugoRune
HugoRune

Reputation: 13829

I think there is a misconception about finalizers here: The finalizer does not have to deal with half-garbage-collected objects.

All managed objects that are accessed by the finalizer will not be garbage-collected before the finalizer runs. It works like any other method in this regard, the GC will only collect objects when they are no longer needed anywhere.

Reachability

One of the guidelines for finalization is that a Finalize method shouldn't touch other objects. People sometimes incorrectly assume that this is because those other objects have already been collected. Yet, as I have explained, the entire reachable graph from a finalizable object is promoted.

The real reason for the guideline is to avoid touching objects that may have already been finalized. That's because finalization is unordered.

Chris Brumme on finalization

So you can reliably access managed objects from the finalizer, with one caveat:
The only thing that is unpredictable is the order in which the finalizers run. So if the row object and the db-connection object are both disposable, and neither of them is disposed manually, then either finalizer may run before the other; so the row-object may find the database connection already closed. But you would run into the same problems if you manually disposed the db-connection before the row. There is no way to tell the compiler that this object must always be disposed before that object.

A workaround would be to let the db-connection object keep a reference for all the row-objects, and dispose each of them in its own finalizer. That way it does not matter which finalizer runs first: if the row finalizer runs first it will still have a db-connection; if the connection finalizer runs first, it will dispose the rows. In the later case, the row finalizer needs to recognize that there is nothing left to do and simply return.

The other option is to create a new database connection inside the finalizer, instead of keeping one stored. However you need to take care: if the finalizer takes a long time to complete, and you continuously create new objects without disposing them, you may run out of memory because freeing objects takes longer than creating them.

Upvotes: 0

Ben Reich
Ben Reich

Reputation: 16324

If you are implementing this IDisposable yourself, you can make all the methods you don't want to expose private or protected, and then only expose them through wrapper methods that have an Action<T> as a parameter, which would just execute the action in a using block. This pattern forces you to use the wrapper methods, which means you can't forget the using block. It is clunkier and requires a bunch of boilerplate, though.

Upvotes: 0

usr
usr

Reputation: 171246

You need to ensure that all objects you want to use in the finalizer are not destroyed yet. You can do this by allocating a GCHandle that is a strong root to those objects. Or, you can root them from something else such as a static field.

Make sure that this GCHandle is cleaned up eventually or else you'll permanently leak some objects (and a handle).

Upvotes: 0

Related Questions