Best Practice: verify the safe cleanup of your unmanaged code
After elaborating on the topic on my first post (see comments), it’s time to wrap the all thing up.
As I’ve noted, the purpose was to brought up some points of interest about developing classes which wrap unmanaged code inside them. When I’m reviewing my teammates code, any usage of unmanaged code triggers me as I start thinking about all the scenarios of which the unmanaged code stays un-handled and therefore causes memory leaks in the application. One of the scenarios which can cause a memory leak was the one we’ve talked about(again, the code in the first post was only for teaching purpose, so this post will be more relevant), but this could be better handled by implementing “Dispose pattern” on the (wrapper)class:
public class CustomWriter : IDisposable
{
private bool disposed = false; // Track whether Dispose has been called.
private StreamWriter m_sw;
public CustomWriter(string filePath, int numberOfLinesToWrite)
{
m_sw = new StreamWriter(filePath);
// If we know that an exception may occur after initializing unmanaged code – try-catch is preferable.
try
{
if (numberOfLinesToWrite < 0)
{
throw new ArgumentException(“What’s wrong with you ?!!? numberOfLinesToWrite can’t be less than 0”, “numberOfLinesToWrite”);
}
// Assume that I’m using numberOfLinesToWrite here…
}
catch // equals to catch(Exception)
{
// Explicit disposal of the object – deterministic.
Dispose();
throw; // rethrow the exception without losing the stack trace.
}
}
#region IDisposable Members
// 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);
}
private void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!disposed)
{
// If disposing equals true – Explicit disposal is called.
// Dispose managed resources.
if(disposing)
{
// clean up managed resources (if have any).
}
// Call the appropriate methods to clean up unmanaged resources here.
m_sw.Close();
disposed = true;
}
}
~CustomWriter()
{
// nondeterministic
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
#endregion
}
Summing up:
Now, if you’re a consumer of a wrapper class, make sure to call the Dispose method (Explicit Dispose) via using or try-finally{Dispose}.
- Remember that the destructor\finalizer (~CustomWriter()) in C# is nondeterministic; This is an important one as the unmanaged code will stay unhandled until the GC(Garbage collector) will decide otherwise.
- “Explicit Dispose” is your goal while using wrapper classes, especially if you’re developing an ASP.NET application which is hosted by a single process (aspnet_wp.exe\w3wp.exe) which hosts many applications (AppDomains) as the nondeterministic nature of the (C#) class destructor\finalizer. In a console\Windows Forms application, each application has its own process, and shutting them down will call the objects destructors(if have any) thus making it harder to detect memory leaks.
- Implementing a destructor\finalizer will harm your performance as the GC has to handle this class instances in a special manner, but this is still much better than memory leaks in your application – more about it at Joe’s article (link at the end of my post).
- If you’re a developer – while writing the wrapper classes or using them, make sure that you call\implement the Dispose method as needed.
- If you’re a code reviewer – make sure to add the appropriate tests to you code review list, so you won’t forget to check them.
More on the subject:
You should read this article by Joe Duffy, a technical program manager of the CLR team, about “Dispose, Finalization, and Resource Management“.
* The article is quite long and hard to follow, but I’m sure that a quick scan can teach you a couple of things.