The primary use of the IDisposable
interface is to release resources - such as unmanaged objects (such as database connections or native library objects).
Normally, the garbage collector will release the memory allocated to a managed object when that object is no longer used, but this is not predictable or guaranteed.
Also, the garbage collector has no knowledge of unmanaged resources.
Good thing we have IDisposable
.
When the life of an IDisposable
object is scoped, it is best practice to use the using
statement.
The using
statement calls the Dispose method on the object.
Suppose we start with the following code (comments omitted for brevity):
using System;
namespace Practice
{
class DisposeCaller : IDisposable
{
private bool disposed = false;
public void Dispose() // Implementation of IDisposable
{
if (disposed)
{
Console.WriteLine("DisposeCaller.Dispose() called AGAIN");
return;
}
disposed = true;
Console.WriteLine("DisposeCaller.Dispose() called");
}
}
class DisposeWrapper
{
private DisposeCaller disposeCaller = new DisposeCaller();
public void Method()
{
Console.WriteLine("DisposeWrapper.Method() called");
using (disposeCaller);
}
~DisposeWrapper()
{
Console.WriteLine("~DisposeWrapper() Called");
disposeCaller?.Dispose();
}
}
class Program
{
static void Main(string[] args)
{
var dw = new DisposeWrapper();
dw.Method();
dw.Method();
}
}
}
What will happen to the DisposeCaller
member when Method()
is called?
disposeCaller
gets disposed - meaning there is no point to call Method()
again.
Let's run the code and see...
Practice.exe DisposeWrapper.Method() called DisposeCaller.Dispose() called DisposeWrapper.Method() called DisposeCaller.Dispose() called AGAIN
What I find particularly interesting is that the finalizer (~DisposeWrapper()
) is never hit.
I guess the best practice is to implement IDisposable
and appropriately constrain the lifecycle of objects using scope.
Maybe your luck will be different if you try it yourself.