Scenario/Problem: | You need to ensure that managed resources (such as database connections) get cleaned up when needed.
Many
types of resources are limited (files, database connections, bitmaps,
and so on) but have .NET classes to manage them. Like unmanaged
resources, you still need to control their lifetimes, but because they
are wrapped in managed code, finalizers are not appropriate. |
Solution: | You need to use the dispose pattern. Let’s look at a similar example as the last section, but with a managed resource. The IDisposable interface defines a Dispose method, but to use the pattern correctly you need to define a few more things yourself:
public class MyWrappedResource : IDisposable
{
//our managed resource
IDbConnection _conn = null;
public MyWrappedResource(string filename)
{
}
public void Close()
{
Dispose(true);
}
public void Dispose()
{
Dispose(true);
}
private bool _disposed = false;
protected void Dispose(bool disposing)
{
//in a class hierarchy, don't forget to call the base class!
//base.Dispose(disposing);
if (!_disposed)
{
_disposed = true;
if (disposing)
{
//cleanup managed resources
if (_conn!=null)
{
_conn.Dispose();
}
}
//cleanup unmanaged resources here, if any
}
}
}
|
When using the dispose pattern, you call Dispose yourself when you’re done with the resource:
//use try-finally to guarantee cleanup
MyWrappedResource res = null;
try
{
res = new MyWrappedResource("TestFile.txt");
}
finally
{
if (res != null)
{
res.Dispose(true);
}
}
This pattern is so common that there is a shortcut syntax:
using (MyWrappedResource res = new MyWrappedResource("TestFile.txt"))
{
//do something with res
}
Note
You should not use
the Dispose pattern with Windows Communication Framework objects.
Unfortunately, they are not implemented according to this pattern. You
can find more information on this problem by doing an Internet search
for the numerous articles out there detailing this problem.
Use Dispose with Finalization
Whereas finalizers should not touch managed objects, Dispose can (and should) clean up unmanaged resources. Here’s an example:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Data;
namespace Dispose
{
public class MyWrappedResource : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall,
SetLastError = true)]
public static extern IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
//IntPtr is used to represent OS handles
IntPtr _handle = IntPtr.Zero;
//our managed resource
IDbConnection _conn = null;
public MyWrappedResource(string filename)
{
_handle = CreateFile(filename,
0x80000000, //access read-only
1, //share-read
IntPtr.Zero,
3, //open existing
0,
IntPtr.Zero);
}
~MyWrappedResource()
{
//note: in real apps, don't put anything in
//finalizers that doesn't need to be there
Console.WriteLine("In Finalizer");
Dispose(false);
}
public void Close()
{
Dispose(true);
}
public void Dispose()
{
Dispose(true);
}
private bool _disposed = false;
protected void Dispose(bool disposing)
{
//in a class hierarchy, don't forget
//to call the base class!
//base.Dispose(disposing);
Console.WriteLine("Dispose({0})", disposing);
if (!_disposed)
{
_disposed = true;
if (disposing)
{
//cleanup managed resources
if (_conn!=null)
{
_conn.Dispose();
}
GC.SuppressFinalize(this);
}
//cleanup unmanaged resources, if any
if (_handle!=IntPtr.Zero)
{
CloseHandle(_handle);
}
}
}
}
}
Here’s a little program to demonstrate what happens:
static void Main(string[] args)
{
using (MyWrappedResource res = new MyWrappedResource("TestFile.txt"))
{
Console.WriteLine("Using resource...");
}
MyWrappedResource res2 = new MyWrappedResource("TestFile.txt");
Console.WriteLine("Created a new resource, exiting");
//don't cleanup--let finalizer get it!
}
It gives the following output:
Using resource...
Dispose(True)
Created a new resource, exiting
In Finalizer
Dispose(False)