ScopeSwitcher

UPDATE (8/6/2008, 8:39 PM): Based on some comments, I've changed the implementation slightly to use FirstOrDefault().

One of the first things I figured out when I got into .NET was how to switch the cursor to the hourglass icon. I know, that's not a very earth-shattering thing to do, but it's something I always did in VB, so I wanted to know how to do it. As it turned out, it's not that hard:

Cursor.Current = Cursors.WaitCursor;

Of course, I wanted to make sure that the cursor went back to the original value once I was done with my code, so I wrote this:

var current = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;

try
{
   // Do interesting code here...
}
finally
{
   Cursor.Current = current;
}

If you're OK that you can "misuse" IDisposable, then you can create a little utility class:

public sealed class CursorSwitcher : IDisposable
{
   private Cursor current = Cursor.Current;
 
   public CursorSwitcher(Cursor newValue) 
      : base()
   {
      Cursor.Current = newValue;
   }
 
   public void Dispose()
   {
      Cursor.Current = this.current;
   }
}

This packages up the cursor switching nicely:

using(var cursorSwitcher = new CursorSwitcher(Cursors.WaitCursor))
{
   // Do interesting code here...
}

However, changing the cursor value isn't the only time I've wanted to switch out a value. There have been numerous situations where I've wanted to swap a property value with another one for a specific section of code, and then switch it back. So, let's generalize the pattern:

public sealed class ScopeSwitcher<TTarget, TNew> : IDisposable
{
    private bool isPropertyStatic;
    private TNew newValue;
    private TNew oldValue;
    private PropertyInfo property;
    private TTarget target;

    public ScopeSwitcher(TNew newValue)
        : base()
    {
        this.FindProperty(BindingFlags.Static);
        this.isPropertyStatic = true;
        this.ExchangePropertyValue(newValue);
    }

    public ScopeSwitcher(TNew newValue, string propertyName)
        : base()
    {
        if(string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("No property name was given.", "propertyName");
        }

        this.FindProperty(BindingFlags.Static, propertyName);
        this.isPropertyStatic = true;
        this.ExchangePropertyValue(newValue);
    }
    
    public ScopeSwitcher(TTarget target, TNew newValue)
        : base()
    {
        if(target == null)
        {
            throw new ArgumentNullException("target");
        }
        
        this.FindProperty(BindingFlags.Instance);
        this.target = target;
        this.ExchangePropertyValue(newValue);
    }

    public ScopeSwitcher(TTarget target, TNew newValue, string propertyName)
        : base()
    {
        if(target == null)
        {
            throw new ArgumentNullException("target");
        }

        if(string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("No property name was given.", "propertyName");
        }
        
        this.FindProperty(BindingFlags.Instance, propertyName);
        this.target = target;
        this.ExchangePropertyValue(newValue);
    }

    private void ExchangePropertyValue(TNew newValue)
    {
        this.newValue = newValue;
        
        if(this.isPropertyStatic)
        {
            this.oldValue = (TNew)this.property.GetValue(null, null);
            this.property.SetValue(null, this.newValue, null);                    
        }
        else
        {
            this.oldValue = (TNew)this.property.GetValue(this.target, null);
            this.property.SetValue(this.target, this.newValue, null);                                
        }
    }
    
    private void FindProperty(BindingFlags flags)
    {
         this.property = (from property in typeof(TTarget).GetProperties(
                             BindingFlags.Public | flags)
                          where property.PropertyType.IsAssignableFrom(typeof(TNew))
                          where property.CanRead && 
                             property.GetGetMethod() != null && 
                             property.GetGetMethod().IsPublic
                          where property.CanWrite && 
                             property.GetSetMethod() != null && 
                             property.GetSetMethod().IsPublic
                          select property).FirstOrDefault();

        if(this.property == null)
        {
            throw new PropertyNotFoundException(
                "A read/write property was not found that was assignable from " + 
                typeof(TNew).FullName + ".");
        }
    }

    private void FindProperty(BindingFlags flags, string propertyName)
    {
         this.property = (from property in typeof(TTarget).GetProperties(
                             BindingFlags.Public | flags)
                          where property.Name == propertyName
                          where property.PropertyType.IsAssignableFrom(typeof(TNew))
                          where property.CanRead && 
                             property.GetGetMethod() != null && 
                             property.GetGetMethod().IsPublic
                          where property.CanWrite && 
                             property.GetSetMethod() != null && 
                             property.GetSetMethod().IsPublic
                          select property).FirstOrDefault();

        if(this.property == null)
        {
            throw new PropertyNotFoundException(
                "A read/write property with name " + propertyName +
                " was not found that was assignable from " + typeof(TNew).FullName + ".");
        }
    }

    public void Dispose()
    {
        if(this.property != null)
        {
            if(this.isPropertyStatic)
            {
                this.property.SetValue(null, this.oldValue, null);
            }
            else
            {
                this.property.SetValue(this.target, this.oldValue, null);
            }
        }
    }
}

That's a lot of code to digest (you can get all of the code plus tests here) but what's nice is that you can do something like this:

using(var switcher = new ScopeSwitcher<Cursor, Cursor>(Cursors.WaitCursor))
{
   // Do interesting code here...
}

Or, if you had a class like this:

public sealed class StringData
{
   private StringData()
      : base()
   {
   }
 
   public static string Data
   {
      get;
      set;
   }
}

You can use the same class:

using(var switcher = new ScopeSwitcher<StringData, string>("new data."))
{
   // Do interesting code here...
}

If the class has a number of properties that match the value of TNew, you can specify the name of the property to switch. So, if StringData was updated like this:

public sealed class StringData
{
   private StringData()
      : base()
      {
      }
 
   public static string Data
   {
      get;
      set;
   }

   public static string Value
   {
      get;
      set;
   }
}

You can choose which property to change:

using(var switcher = new ScopeSwitcher<StringData, string>("new data.", "Value"))
{
   // Do interesting code here...
}

This also works for instance values and not just static properties - check the tests for details.

By the way, if you're wondering why I didn't define StringData to be a static class, just read this post for all the gory details :).

Let me know if you have any issues with it - enjoy!

* Posted at 08.01.2008 01:51:05 PM CST | Link *

Blog History