0

(Note: this question is related to Calling GC.SuppressFinalize() from within a finalizer but is not a duplicate, as this question is explicitly specifically about the case of no managed resources, whereas that one is explicitly about when having both managed and unmanaged resources.)

The classic Disposable Pattern in C# goes roughly like this:

public class MyClass : IDisposable {
    bool alreadyDisposed = false;

    void Dispose(bool calledFromDisposeMethod) {
        if (!alreadyDisposed) {
            alreadyDisposed = true;
            if (calledFromDisposeMethod) {
                GC.SuppressFinalize(this);
                // Release _managed_ resources here
            }
            // Release _unmanaged_ resources here
        }
    }

    public void Dispose() => Dispose(true);
    ~MyClass() => Dispose(false);
}

The rationale is that when an instance of MyClass is disposed of explicitly (by calling the Dispose() method - possibly, but not necessarily, by using using) then we want to release all resources, managed and unmanaged alike, whereas when the instance is garbage-collected without having already been disposed of then we only want to release unmanaged resources, leaving managed resources to be taken care of elsewhere (e.g. letting them be until they themselves are also garbage-collected).

By the way, note the call GC.SuppressFinalize(this), which tells the garbage collector there's no need to call the finalizer if and when the instance gets garbage-collected, as Dispose() has already been called and taken care of releasing resources. Because of the use of the alreadyDisposed flag there's no real danger if the finalizer does get called, but it's unnecessary, and letting the garbage collector know that allows it to skip putting the instance in the finalization queue, thus potentially freeing it (and other stuff it references) faster.

But now what about the case where we only have unmanaged resources? I would expect to find the following, somewhat simpler pattern:

public class MyClass : IDisposable {
    bool alreadyDisposed = false;

    void Dispose() {
        if (!alreadyDisposed) {
            alreadyDisposed = true;
            // Release (unmanaged) resources here
            GC.SuppressFinalize(this);
        }
    }

    ~MyClass() => Dispose();
}

Note once again the call GC.SuppressFinalize(this), which has the same function as above. In this case it might end up being called (indirectly) from the finalizer itself, but as far as I know there's no harm in that.

This form of the Disposable Pattern seems simpler, and is, as far as I can tell, perfectly adequate - when there are no managed resources involved. And yet, I haven't seen it anywhere, or at least I haven't seen it condoned anywhere.

Does this pattern make sense, or is it fundamentally flawed?

Tom
  • 4,910
  • 5
  • 33
  • 48
  • 1
    It’s still needed because you haven’t marked the class as sealed. – Dave M Jul 07 '19 at 17:30
  • Too 'roughly', the method should be `protected virtual void Dispose(bool)`. Inheritance is a key point. – H H Jul 07 '19 at 17:33
  • 2
    As Henk points out, there's an elephant in the room we're not talking about: *why are you even considering writing a class that disposes of unmanaged resources and has a finalizer*? This is an XY problem. An XY problem is when you have a bad idea for how to solve a problem, and you're asking a question about the bad solution. Ask a question about the real problem; odds are good that the answer to that question will be "use a safe handle". What's the real problem? – Eric Lippert Jul 07 '19 at 20:41
  • As I understand from the comments above and from the one answer given so far, one problem (maybe not the only one, but this comment is about this one) is the use of such patterns in order to handle unmanaged resources, which are probably handled better some other way (safe handles). But is that really specific to the pattern presented in the question? Couldn't the same be said about the traditional Disposable Pattern? The way I see it, they differ in that in this one there are no _managed_ resources - but both deal with _unmanaged_ ones! What am I missing? – Tom Jul 08 '19 at 14:36
  • @EricLippert: as you say, it might very well be that such an approach is not the right way to go about specific given use cases, and, as you also say, whether such an approach is appropriate for a given use case is another question. I'll take that under advisement, and might ask about my "real" problem at some different place and time. Thanks! – Tom Jul 08 '19 at 14:39
  • @HenkHolterman: thanks for your comment. I think it's important when someone asks a bad question to explain to them what it is that makes it bad so that they can improve their question or at least learn how to ask better questions in the future. Right now, I honestly don't understand. Can you explain what is sloppy in my code? I'll be very happy to improve it! As for x/y: no, I asked about something theoretical and very specific, and that's exactly what it's about. Whether that specific thing is the right solution to some real-life problem is another question to be asked elsewhere. – Tom Jul 09 '19 at 06:44
  • Your perspective is off. Most .net applications have 0 (zero) unmanaged resources. A large, hardware oriented project may need one or two. And then you don't want to simplify their use, you'll want to bulletproof it. – H H Jul 09 '19 at 07:05
  • I see, thanks. What I still find strange is that I took the traditional Disposable Pattern as a starting point, removed _managed_ resources from it, and now am told that the problem is _unmanaged_ resources - which were part of the _original_ pattern as well, and not something that I introduced! :D I guess I'm still missing something. – Tom Jul 09 '19 at 08:50

0 Answers0