Introduction
The "using" block in C# and VB.NET can be used as a high-level language construct, not just as a way of tidying-up. In this article I show you how to capture and restore state using constructors and disposers.
Background
In any substantial application, especially those with a UI, it is frequently necessary to change state to perform some task and to then restore the state after - even if an error occurs. This usually involves a Try..Catch..Finally block and a lot of boilerplate code. If the implementation of the state changes then everywhere that captures and restores the state must be changed too. Think of simple things like capturing and restoring the selection in a combo box, or changing
the cursor to an hourglass and back again. A "nicer" approach would be to have some sort of language construct that surrounds the portion of code that occurs in the alternate state. In psuedocode such a construct might look like this:
...
With(AlternateState)
{
}
...
The C# and VB.NET don't provide an easy way of adding language features like that, so the basis of the solution I have chosen is the "using" block (
Using...End Using in VB.NET or
using{...} in C#). In C# the usage is thus:
using(System.IDisposable myObject = new MyDisposableType())
{
}
This is equivalent to the following code:
System.IDisposable myObject = new MyDisposableType();
try
{
}
finally
{
if(myObject != null)
{
myObject.Dispose();
}
}
The only requirement to be able to use this construct is that the object used implements the
System.IDisposable
interface. So, we have a constructor that is run at the start of the block and a
Dispose
method called at the end of the block. These provide the opportunity to capture and restore state respectively. If we are to fully implement the psuedocode example above then we also need to change state in the constructor, although I shan't be forcing that point.
Using the Code
In the code I provide implementations of three example cases:
ComboBoxState
- Capture and restore the selected item in a combo box.
CursorState
- Change and restore the cursor during an operation.
ImpersonateUser
- Impersonate a user and restore the original user once the task is over.
The point of the three examples in the code is show how the state can be captured and changed in the constructor of the helper classes and then restored in the dispose method.
I have used all three (in rather artificial circumstances) in
TestForm
search for "
EXAMPLE:
" in the code to find them.
Points of Interest
So, what interesting points can we take from the first version of this demo code? Well, in the test form you can see a couple of simplistic methods for handling the setting of control properties from arbitrary threads - this shouldn't actually be a problem in this app. because the timer control raises events on form's main thread, but it is worth knowing about. There are probably some useful bits and pieces in the impersonation code too. This is quite old know and may contain work-arounds for bugs that no loner exists - I have included links back to the original articles to explain some of the more esoteric bits.
The main gotcha to watch out for though is unexpected changes in the state of objects prior to the dispose methods. Remember that the method may be called from an error handler or destructor and that controls or forms that have been captured in the constructor may have been disposed of themselves before you get to them. To combat this you need to code defensively and check properties such as
IsHandleCreated.
History
Version 1.0
- Initial version. It compiles and runs on my machine.