Reputation: 1279
I've been reading about the Dispose pattern, and I kinda understand what's it for (cleaning up resources so my application won't leak memory) but I'd like to see it in a practical example.
My idea is to write a simple application that, first, uses some resources and does not dispose of them, and after some code changes, properly disposes those resources. What I'd like to see is the memory usage before/after the code changes to visualize how disposing helps.
Question: What objects can I use? I tried to do it with some big images (JPEG images 15+ MB in size) but I can't build a practical example out of it. Of course I'm open to other ideas.
Upvotes: 0
Views: 1974
Reputation: 29212
This unit test does what you're describing. It does the same thing both with and without calling Dispose
to show what happens without calling Dispose
.
Both methods create a file, open the file, write to it, then open it again and write to it again.
The first method throws an IOException
because the StreamWriter
wasn't disposed. The file is already open and can't be opened again.
The second one disposes before trying to reopen the file, and that works without an exception.
[TestClass]
public class DisposableTests
{
[TestMethod]
[ExpectedException(typeof(IOException))]
public void DoesntDisposeStreamWriter()
{
var filename = CreateFile();
var fs = new StreamWriter(filename);
fs.WriteLine("World");
var fs2 = new StreamWriter(filename);
fs2.WriteLine("Doesn't work - the file is already opened.");
}
[TestMethod]
public void DisposesStreamWriter()
{
var filename = CreateFile();
var fs = new StreamWriter(filename);
fs.WriteLine("World");
fs.Dispose();
var fs2 = new StreamWriter(filename);
fs2.WriteLine("This works");
fs2.Dispose();
}
private string CreateFile()
{
var filename = Guid.NewGuid() + ".txt";
using (var fs = new StreamWriter(filename))
{
fs.WriteLine("Hello");
}
return filename;
}
}
You likely wouldn't see the problem with memory use because that's not specifically what IDisposable
addresses. All objects use memory, but most aren't disposable. The garbage collector reclaims memory from objects that are no longer referenced (like an object that's created in a method but goes out of scope when the method ends.)
IDisposable
is for scenarios where the class holds onto some resource that isn't garbage collected. Another example is SqlConnection
. It's not about memory, it's about the connection to the SQL Server. There are only so many available. (It will eventually get released, but not in a predictable manner - that's a digression.)
The exact reasons why classes might be IDisposable
vary. They often have nothing in common. IDisposable
doesn't care what the reason is. It just means that there's something in the class that needs to be cleaned up.
Upvotes: 3
Reputation: 47530
The following example show the general best practice to implement IDisposable interface. Create some MyResouce
instances and test it without calling Dispose
method, there is a memory leak. Then call the Dispose everytime once you are finish with MyResouce
instance, no memory leak. Reference
public class DisposeExample
{
// A base class that implements IDisposable.
// By implementing IDisposable, you are announcing that
// instances of this type allocate scarce resources.
public class MyResource: IDisposable
{
// Pointer to an external unmanaged resource.
private IntPtr handle;
// Other managed resource this class uses.
private Component component = new Component();
// Track whether Dispose has been called.
private bool disposed = false;
// The class constructor.
public MyResource(IntPtr handle)
{
this.handle = handle;
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
component.Dispose();
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
CloseHandle(handle);
handle = IntPtr.Zero;
// Note disposing has been done.
disposed = true;
}
}
// Use interop to call the method necessary
// to clean up the unmanaged resource.
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
// Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~MyResource()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
}
public static void Main()
{
// Insert code here to create
// and use the MyResource object.
}
}
Upvotes: 1