Removing the System.IDisposable object in my finalizer

There are several discussions on StackOverflow about what to do if my object manages other managed objects that implement System.IDisposable .

Note. Below I am not talking about unmanaged code. I fully understand the importance of cleaning up unmanaged code.

Most discussions say that if your object owns another managed object that implements System.IDisposable , then you should also implement System.IDisposable , in which case you should call Dispose() on the one-time objects that your object has. This would be logical because you do not know if your one-time object uses unmanaged code. You only know that the creator of another object thought it would be wise if you typed Dispose as soon as you no longer need the object.

A very good explanation of the one-time template was given here on StackOverflow, edited by the community wiki:

Proper use of the IDisposable interface

Quite often, as well as in the mentioned link, I read:

"You do not know the order in which two objects are destroyed. It is entirely possible that in your Dispose() code, the managed object you are trying to get rid of does not exist anymore."

This puzzles me because I thought that as long as any object contains a reference to object X, object X will not and cannot be completed.

Or in other words: as long as my object contains a reference to object X, I can be sure that object X is not complete.

If this is so, then why could it be so: if I keep a reference to my object until I finish, is the object I refer to already completed?

+4
source share
5 answers

The truth is somewhere between the two:

  • The object cannot be garbage collected, so the possibility that the object is no longer “present” is not true
  • An object can be finalized if links to it are no longer available from other objects that are not subject to final revision.

If the object X belongs to the object Y, but both of them can be finally defined, then it is quite possible that the object Y must be finalized to the object X or even for their final completion.

If your assumption was correct, then you could create two objects that are related to each other (and have finalizers), and they can never be garbage, because they can never be completed.

+5
source

Quote Eric Lippert , When Everything You Know Is Mistaken, Part Two

Myth: saving a reference to an object in a variable prevents the finalizer from running while the variable is alive; the local variable is always alive, at least until the control leaves the block in which the local one.

 { Foo foo = new Foo(); Blah(foo); // Last read of foo Bar(); // We require that foo not be finalized before Bar(); // Since foo is in scope until the end of the block, // it will not be finalized until this point, right? } 

The C # specification states that the runtime environment allows wide latitude to detect when the repository containing the link will never be available again and stop processing this repository as the root of the garbage collector. For example, suppose we have a local variable foo , and the link is written to it at the top of the block. If the jitter knows that a particular read is the last read of this variable, this variable can be legally removed from the GC root set immediately; it should not wait until the control leaves the scope of the variable. If this variable contained the last reference, then the GC may detect that the object is inaccessible and placed in the finalizer queue immediately. Use GC.KeepAlive to avoid this.

Why does jitter have this latitude? Assuming the local variable is registered in the register, you need to pass the value of Blah() . If foo is in the register that Bar() should use, it makes no sense to keep the value should never be read - again foo on the stack until Bar() is called equally. (If the actual data of the code generated by the jitter is of interest to you, see Raymond Chance for a more in-depth analysis of this problem .)

An additional bonus pleasure: the runtime uses less aggressive code generation and less aggressive garbage collection when you run the program in the debugger, because it is a bad debugging experience for creating objects that you are debugging suddenly, even if the variable is referencing an object in the area. This means that if you have an error where the object ends too early, you probably cannot reproduce this error in the debugger!

See the last paragraph in this article for an even more terrible version of this problem.

+1
source

If everything is done correctly, you do not need to worry about deleting objects that are already located. Each Dispose implementation should just do nothing if it was previously removed.

This way, you cannot know if any child objects have already been installed or completed (since the completion order is random, see another post), but you can safely call them Dispose anyway.

0
source

After all the answers, I created a small program that shows what Jodrell wrote (thanks to Jodreka!)

  • An object can be garbage collected as soon as it is not used, even if I have a link to it
  • This will be done if not debugging.

I wrote a simple class that allocates unmanaged memory and a MemoryStream. The latter implements System.IDisposable.

For all of StackOverflow, I have to implement System.IDisposable and free up unmanaged memory, and also Dispose a managed memoryStream if my Dispose is called, but if my finalizer is called, I have to free unmanaged memory.

I am writing several diagnostic console messages

 class ClassA : System.IDisposable { IntPtr memPtr = Marshal.AllocHGlobal(1024); Stream memStream = new MemoryStream(1024); public ClassA() { Console.WriteLine("Construct Class A"); } ~ClassA() { Console.WriteLine("Finalize Class A"); this.Dispose(false); } public void Dispose() { Console.WriteLine("Dispose()"); this.Dispose(true); GC.SuppressFinalize(this); } public void Dispose(bool disposing) { Console.WriteLine("Dispose({0})", disposing.ToString()); if (!this.IsDisposed) { if (disposing) { Console.WriteLine("Dispose managed objects"); memStream.Dispose(); } Console.WriteLine("Dispose unmanaged objects"); Marshal.FreeHGlobal(memPtr); } } public bool IsDisposed { get { return this.memPtr == null; } } } 

This program follows the Dispose Pattern, as described many times, ao here in stackoverflow in the Proper Use of the IDisposable Interface

by the way: for simplicity, I excluded exception handling

A simple console program creates an object, does not use it, but saves a link to it and forces the garbage collector to collect:

 private static void TestFinalize() { ClassA a = new ClassA() { X = 4 }; Console.WriteLine("Start Garbage Collector"); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Done"); } 

Note that the variable a contains a reference to the object until the end of the procedure. I forgot to Dispose, so my finalizer must take care of the disposal

Call this method from the main one. Run the release from the debugger and run it using the command line.

  • If you start from the debugger, the object remains valid until the end of the procedure, so until the garbage collector finishes collecting
  • If you run from the command line, then the object is completed before the procedure is completed, although I still have a reference to the object.

So Jodrell is right:

  • Unmanaged code requires Dispose () and Finalize, use Dispose (bool)

  • Managed disposable objects need Dispose (), preferably through Dispose (bool). In Dispose (bool), only Dispose () of managed objects is called, if the utility

  • they don’t trust the debugger: it finishes work on objects at different points in time than without a debugger

0
source

In most cases, when Finalize is called on an object that contains references to one or more IDisposable objects, one or more of the following apply:

  • The other object has already been cleared, in which case the Dispose call is useless at best.
  • Another object should be completed as soon as possible, but not yet, in which case the Dispose call is probably not needed.
  • Another object cleanup code cannot be used safely in the context of finalizer finalization, in which case the Dispose call can be catastrophic.
  • Another object is still being used by code elsewhere, in which case the Dispose call might be catastrophic.

There are several situations where the code knows enough about the IDisposable objects it deals with in order to know that none of the above actions are applied, or that it should trigger a cleanup, despite the foregoing; however, these situations may be better satisfied that other objects supply a method other than Dispose , which can be called by the terminated object.

0
source

All Articles