Here is a common pattern of code:
// set some temporary state for the following block
try
{
// do some stuff
}
finally
{
// un-set the temporary state set above
}
A perfect example is showing the wait cursor during an operation in a Windows Forms application:
this.UseWaitCursor = true;
try
{
// do some stuff
}
finally
{
this.UseWaitCursor = false;
}
Every time I write one of these, I think there must be a better way to do this, something more akin to the old CAutoPtr template class in C++/ATL. The CAutoPtr class worked because C++ (regular C++, not the managed version) has deterministic calling of destructors. This is to say, that when an object is freed, its destructor is called immediately. This is in contrast with managed code which is garbage collected.
In managed code (C#, VB.NET, etc.) when the last reference to an object is release, via setting the reference to null or when the reference variable goes out of scope, the object’s memory is not actually released right away, and the destructor is not called either. Instead, the object sits on the heap in the same state it was when that last reference was released until the .NET runtime decides it is running out of memory and needs to garbage collect. In today’s systems with 4 GB of RAM or more, this often does not happen until the application’s process ends.
To compensate for this and allow developers to explicitly force an object’s destructor to be called immediately, the creators of the .NET Framework provided the IDisposable interface. This article is not about IDisposable so I won’t go in to a long explanation about it. Instead, you should know these basic facts about IDisposable:
- IDisposable is an interface that should (must?) be implemented by any class which manages critical, particularly unmanaged, resources.
- IDisposable has one method, Dispose(), which should clean up any critical resources.
- Unlike most interfaces, both C# and Visual Basic.NET have built-in knowledge and support of the IDisposable interface via the using statement (Using in Visual Basic.NET). When an object that implements IDisposable is wrapped in a using statement, its Dispose() method is called immediately when the using statement goes out of scope. Example:
string text;
// The StreamReader class implements IDisposable
using (StreamReader reader =
new StreamReader("c:\\tmp\\foo.txt"))
{
text = reader.ReadToEnd();
}
// At this point, the using statement goes out of
// scope, which causes the StreamReader's Dispose
// method to be called, which closes the file's
// handle.
Based on this, I knew any CAutoPtr like class would need to use the IDisposable interface in order to take advantage of the using statement, but I never took the time to actually figure out the details. That is, until now. As I have become more proficient at LINQ and lambda expressions, it became clear that the combination of IDisposable and lambda expressions was the answer. Interestingly, my AutoCleanup class itself does not leverage lambda expressions itself anywhere, rather it is the ability to use anonymous functions when constructing the AutoCleanup class that makes this so useful.
Here is the complete AutoCleanup class:
using System;
namespace ITDevWorks.Utility
{
/// <summary>
/// Guarantees automatic execution of delegate functions
/// when an instance of this class is constructed and/or
/// when it is disposed.
/// </summary>
/// <remarks>
/// The <b>AutoCleanup</b> class is used in cases where
/// you would normally use a try/finally to ensure that
/// your cleanup code is executed, regardless of
/// whether an exception was thrown or not. With the
/// <b>AutoCleanup</b> class, instead of all of the
/// try/catch lines which can make code less readable,
/// you can leverage the <see langword="using"/>
/// statement, instantiating an instance of this class,
/// and pass either just the cleanup code or both the
/// constructor code and cleanup code to execute.
/// </remarks>
public class AutoCleanup : IDisposable
{
public delegate void ExecuteAction();
private bool disposed = false;
private ExecuteAction executeOnDispose;
/// <summary>
/// Constructs an <see cref="AutoCleanup"/> object,
/// immediately executing a delegate function, and
/// the guaranteeing the execution of a second
/// deletate function when disposed.
/// </summary>
/// <param name="executeOnConstruct">
/// The delegate function to execute during
/// construction.
/// </param>
/// <param name="executeOnDispose">
/// The delegate function to execute when disposed.
/// </param>
public AutoCleanup(ExecuteAction executeOnConstruct,
ExecuteAction executeOnDispose)
{
if (null != executeOnConstruct)
{
executeOnConstruct();
}
this.executeOnDispose = executeOnDispose;
}
/// <summary>
/// Constructs an <see cref="AutoCleanup"/> object,
/// guaranteeing the execution of a provided delegate
/// function when disposed.
/// </summary>
/// <param name="executeOnDispose">
/// The delegate function to execute when disposed.
/// </param>
public AutoCleanup(ExecuteAction executeOnDispose)
{
this.executeOnDispose = executeOnDispose;
}
#region IDisposable Members
/// <summary>
/// Disposes the <see cref="AutoCleanup"/> object,
/// executing the delegate function provided in the
/// constructor.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
//
// Internal implementation of the Dispose() method.
// See the MSDN documentation on the IDisposable
// interface for a detailed explanation of this
// pattern.
//
private void Dispose(bool disposing)
{
if (!this.disposed)
{
//
// When disposing is true, release all
// managed resources.
//
if (disposing)
{
if (null != this.executeOnDispose)
{
this.executeOnDispose();
}
}
disposed = true;
}
}
#endregion
}
}
To use this class, you construct a new instance of it inside of a using statement, and pass in two parameter-less, anonymous functions. Note that when an anonymous function has not parameters you must provide the open and close parenthesis with nothing between them where the parameter list would normally go.
private void Form1_Shown(object sender, EventArgs e)
{
using (new AutoCleanup(() => this.UseWaitCursor = true,
() => this.UseWaitCursor = false))
{
// do form initialization
}
}
Furthermore, I was able to use AutoCleanup as a base to create specialized derivations of for common use cases. Below is an example that implements the UseWaitCursor logic.
using System;
using System.Windows.Forms;
namespace ITDevWorks.Utility
{
/// <summary>
/// Automatically enables the wait cursor for a Windows
/// Forms control and disables it when the class is
/// disposed.
/// </summary>
public class AutoWaitCursor : AutoCleanup
{
public AutoWaitCursor(Control control)
: base(() => { control.UseWaitCursor = true;
Application.DoEvents(); },
() => { control.UseWaitCursor = false; })
{
// Do Nothing
}
}
}
Like the XML comment shows above, to use this class all you need to do is:
private void Form1_Shown(object sender, EventArgs e)
{
using (new AutoWaitCursor(this))
{
// do form initialization
}
}
This is far simpler and easier to read than the whole try/finally clumsiness.
{ 0 comments }
