0

I am at a loss on how to handle a class that contains variables with Dispose/Finalize methods. I wish for this class to contain its own Dispose/Finalize methods that call upon the Dispose for each variable; however, both the C# documentation and all other answers/examples on StackOverflow are causing some confusion.

The primary confusion comes from the lack of clarification between what's a "managed" or "unmanaged" object. For example, the documentation here that explains how to implement Dispose simply uses placeholder comments that simply state "Free any unmanaged objects here." or "Free any other managed objects here."

Is a class variable with Dispose/Finalize fall under the category of managed or unmanaged? (Furthermore, should I be concerned about class variables that do NOT contain any sort of Dispose/Finalize implementations? Considering how there's two types of "management", does that mean those without "Dispose" still need to be disposed somehow?)

I.e., what would be the correct way to handle this class?

class BaseClass : IDisposable {

   MyDisposeableObject disposeMe; // object with Dispose/Finalize
   Dictionary<string,int> anotherObject; // just some arbitrary other object

   bool disposed = false;

   public BaseClass() {
      disposeMe = new MyDisposeableObject();
      anotherObject = new Dictionary<string,int>();
   }

   public void Dispose() { 
      Dispose(true);
      GC.SuppressFinalize(this);           
   }

   protected virtual void Dispose(bool disposing) {
      if (disposed)
         return; 

      if (disposing) {
         // Free any other managed objects here.
         // Should I use "disposeMe.Dispose()" here?
      }

      // Free any unmanaged objects here.
      // OR should I use "disposeMe.Dispose()" here?

      // Also should I be doing anything with "anotherObject"?

      disposed = true;
   }

   ~BaseClass() {
      Dispose(false);
   }
}
Griffort
  • 1,174
  • 1
  • 10
  • 26

1 Answers1

1

That was also a confusion for me but when I read more about memory management and GC mechanism in .Net everything became clear.

You should call disposeMe.Dispose() only if "disposing=true". Because it is a managed class/resource. I assume that it also implements this dispose and destructor pattern correctly.

Why you should not try to use any managed object outside of the if(disposing) block?

Because GC may not and does not collect your objects by following a graph from owner to owned. So, when the Dispose method is called by Destructor, disposeMe object may already be collected and unreachable. So you can not/should not dispose it in this area.

But you can free unmanaged resources, like unmanaged memory spaces you allocate, handles you opened... Since GC does not know anything about them, it can not collect and free them unless you intentionally free them. If you don't, there will be memory and handle leaks, which will eventually crash the application.

NthDeveloper
  • 969
  • 8
  • 16
  • I've also seen examples where objects such as `anotherObject` are set to null in the Dispose method. Should I follow through with that also (as in, does that objectively help with performance/garbage collection), or is it simply a stylistic choice? – Griffort Sep 29 '18 at 08:47
  • When you call Dispose method manually (disposing=true) you can follow this rule. Set all the references to other objects to null, also set events to null in order to clear the subscriber list. This is for making the life of GC easier. Because it tracks all the references for all objects, if there is no reference to an object, GC can safely collect it. – NthDeveloper Sep 29 '18 at 12:02
  • Clarification: When the deconstructor of your object is being called, other objects your object is referring to via fields or similar may have already been deconstructed (that is, *their* deconstructor has already been called), **but they are not *unreachable***. The act of putting an object on the freachable queue (which is what the finalizer thread is processing) makes them reachable, and thus ineligible for collection, which makes all objects that your object is referring to also ineligible for collection. – Lasse V. Karlsen Sep 29 '18 at 19:19
  • @Lasse, I prefer not to rely on the reference in f(finalize)reachable queue. Because, the documentation says, the reference is removed from this queue after the finalizer thread calls the object's destructor and the object is now subject to garbage collection. Since we the order of calling destructors can not be determined and we can not know when will be the next collection (maybe immediately after the first object's destructor is called), I personally prefer, not to touch managed resources when dispose is called from destructor. – NthDeveloper Oct 01 '18 at 06:36
  • I don't touch the managed references myself, but it isn't completely unsafe to do so. It is unsafe insofar the object may have had its own deconstructor called, but the object is still there, it has not been collected. Objects on the freachable queue is not eligible for collection, as the freachable queue is considered a root. So accessing such a referenced object won't be a catastrophic meltdown, but it may be a `ObjectDisposedException` or similar. – Lasse V. Karlsen Oct 01 '18 at 14:11