Destructor VS Idisposable

What is the difference between using IDisposable vs a destructor in C#?

A finalizer (aka destructor) is part of garbage collection (GC) - it is indeterminate when (or even if) this happens, as GC mainly happens as a result of memory pressure (i.e. need more space). Finalizers are usually only used for cleaning up unmanaged resources, since managed resources will have their own collection/disposal.

Hence IDisposable is used to deterministically clean up objects, i.e. now. It doesn't collect the object's memory (that still belongs to GC) - but is used for example to close files, database connections, etc.

There are lots of previous topics on this:

  • deterministic finalization
  • disposing objects
  • using block
  • resources

Finally, note that it is not uncommon for an IDisposable object to also have a finalizer; in this case, Dispose() usually calls GC.SuppressFinalize(this), meaning that GC doesn't run the finalizer - it simply throws the memory away (much cheaper). The finalizer still runs if you forget to Dispose() the object.

Difference between destructor, dispose and finalize method

Destructor implicitly calls the Finalize method, they are technically the same. Dispose is available with objects that implement the IDisposable interface.

You may see : Destructors C# - MSDN

The destructor implicitly calls Finalize on the base class of the
object.

Example from the same link:

class Car
{
~Car() // destructor
{
// cleanup statements...
}
}

The Destructor's code is implicitly translated to the following code:

protected override void Finalize()
{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}

Your understanding for the Destructor is right:

From MSDN

The programmer has no control over when the destructor is called
because this is determined by the garbage collector
. The garbage
collector checks for objects that are no longer being used by the
application. If it considers an object eligible for destruction, it
calls the destructor (if any) and reclaims the memory used to store
the object. Destructors are also called when the program exits. It is
possible to force garbage collection by calling Collect, but most of
the time, this should be avoided because it may create performance
issues.

If structs can implement IDisposable, why can't they have destructors?

Doesn't this same problem apply to Dispose()?

Sort of, but not entirely. Specifically, the "then its own cleanup will fail" is possible but unlikely, since Dispose() must be safe to call multiple times on the same object, and it will normally not be a problem to call it multiple times on different copies of the same object.

If structs can't have finalizers, why are they allowed to implement IDisposable?

Allowing it is the natural behaviour; it gives the language simpler rules. Since this is not something that a programmer is likely to get wrong by accident, the benefit in writing extra code in the compiler to reject this is small.


Jeroen Mostert adds that it can even make a lot of sense for structs to implement IDisposable:

IDisposable may be implemented simply because it is a requirement of some other code, even though Dispose()'s implementation on this specific type will do absolutely nothing. In this case, there is no risk in accidentally calling it on a different copy. An example of this is when a struct implements IEnumerator<T>, where IEnumerator<T> in turn implements IDisposable.

Finalize vs Dispose

Others have already covered the difference between Dispose and Finalize (btw the Finalize method is still called a destructor in the language specification), so I'll just add a little about the scenarios where the Finalize method comes in handy.

Some types encapsulate disposable resources in a manner where it is easy to use and dispose of them in a single action. The general usage is often like this: open, read or write, close (Dispose). It fits very well with the using construct.

Others are a bit more difficult. WaitEventHandles for instances are not used like this as they are used to signal from one thread to another. The question then becomes who should call Dispose on these? As a safeguard types like these implement a Finalize method, which makes sure resources are disposed when the instance is no longer referenced by the application.

IDisposable with destructor: requires thread-safe implementation?

While it is sometimes possible for an object to be finalized while a seemingly-live reference exists, that can only happen when nothing else is going to ever refer to the object. The GC.SuppressFinalize(this) actively refers to the present object 'this', thus guaranteeing that it will not be finalized until the GC.SuppressFinalize executes. Further, the fact that the object reference existed to dispose the object, and was available to the Dispose method, guarantees that the finalizer couldn't have been queued before Dispose started running unless the object was dead and a finalizer somewhere (either its own finalizer, or that of some other object) resurrected it.

Because there are some scenarios where an object could be scheduled for finalization and resurrected without ever being aware of it, it may not be a bad idea to protect a dispose and finalize against redundant operation. Microsoft's pattern is not a good one, however. Finalizable objects shouldn't hold references to any objects not needed for finalization. If an object would hold a mixture of managed and unmanaged resources, the unmanaged resources should be moved into their own classes (effectively turning them into managed resources), so then the main object would hold nothing but managed resources.

Simple destructor issue (IDisposable interface)

Your question really seems to boild down to 'How do I delete stuff in C#'. The short answer is you can't, that's the job of the Garbage Collector System.GC. The IDisposable inerface is used to ensure that imporant resources that don't belong to .Net (like files, or network/database connections, but not a .Net class), are cleaned up on demand.

The IDisposable interface is important because it allows you to use the using pattern. That is, if your class implements IDisposable it can be used in a using block. Consider for instance:

class MyClass : IDisposable {
public void Dispose() { }
}

this class can now be used like this:

using (MyClass instance = new MyClass())
{

} // instance.Dispose() is called here at the end of the block.

The point of the using block is that when the block ends, it the compiler will throw in a call to Dispose() which you can use to get rid of important resources like files and database connections. For example, all of the Stream classes, like FileStream and what not implement IDisposable because it's a bad idea to leave a file open. Instead, you wrap all of your access in a using block, and then you are guaranteed that FileStream.Dispose will close the file out. Consider:

using (FileStream myFile = File.OpenRead("...")) 
{
// Read the content of the file.

} // The file is guaranteed to be closed here. Cool!

This is much neater than doing something like this:

FileStream stream = File.OpenRead(" ... ");

stream.Close(); // Yes, you closed it manually, but it's error prone. What if you forget to do this?

Now what you're thinking of is a term called "Finalization", that is when the class is actually destroyed. This happens when the garbage collector (the System.GC class) actually destroys objects and cleans up their memory. Consider:

public class MyClass {

// This method, the 'Finalizer' will be called when the class is destroyed.
// The 'finalizer' is essentially just the name of the class with a '~' in front.
~MyClass() {
Console.WriteLine("Destroyed!");
}
}

public class Program {
public static void Main() {
MyClass referenceHeld = new MyClass(); // Reference held
new MyClass(); // No reference held on this class
WeakReference sameAsNoReference = new WeakReference(new MyClass()); // Equivalent to no reference.

System.GC.Collect(); // Force the garbage collector to collect
Console.ReadLine();
}
}

In short, the Garbage collector is the part of the runtime that cleans up stuff that isn't being used. What does it mean to not be used? It means that there are no references attached to the object. For example, if you run the program above, you'll notice that the word "Destroyed" gets printed on the screen twice. That's because two of the instances of MyClass created in the Main function are not pointed to by reference (A WeakReference is essentially the same thing as no reference). When we call GC.Collect() the garbage collector runs and cleans up the references.

That said, you should NOT call GC.Collect on your own. You can, for experimentation and education of course, but most people will tell you that the garbage collector does a fine job of keeping things clean on it's own. It doesn't make sense to have a bunch of GC.Collect scattered throughout your code, because that's the whole point of having a Garbage collector - to not have to worry about cleaning things up yourself.

So in short, you really can't destroy objects on your own, unless you call GC.Collect() (which you shouldn't do). The IDisposable interface allows you to work with the using pattern, ensuring that important resources are released (This is not the same thing as destroying the object though! All IDisposable does is ensure that Dispose() is called when the using block exits so you can clean up important stuff, but the object is still alive - an important distinction).



Related Topics



Leave a reply



Submit