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 02:51:05 PM (Last Update: 08.06.2008 09:43:02 PM) | 4 comments | Link | RSS *

Comments

# Feels Dirty, from Eric at 08.02.2008 01:07:34 AM

I don't know... it feels dirty using 'using' and IDisposable for anything other than freeing resources. Have you seen how they are using 'using' for creating form begin/end tags in ASP.NET MVC?

# Mr., from John at 08.02.2008 12:41:55 PM

Jason,
I have a comment on the FindProperty method
when you find the property using LINQ. Can you use
the FirstOrDefault() instead of just First().
FirstOrDefault() will give you a null value instead of
throwing an exception. You can check if's null
then throw the exception.
I heard that everytime we put try catch block, it's adding
unnecessary overhead.
But I might be wrong or misunderstood the concept.

# --, from John Jabot at 08.02.2008 09:23:13 PM

I have a comment about the FindProperty method when you use LINQ. Would it better if you use FirstOrDefault() instead of First() method. The FirstOrDefault method will return null instead of throwing an exception, then you will thrown an exception instead of having a try catch block in the code. I thought the try catch adds an overhead to the code.

# Replies, from Jason Bock at 08.04.2008 08:34:41 AM

John,

Yes, you're right, that would work too, and it's probably better. In either case, I'd still throw the custom exception.

Eric,

It's OK :). After reading that post from EricG I realized that using "using" for things other than cleaning up unmanaged resources is kosher. It allows from some really nice designs.

Add a Comment

(*) = Required field
Name (*):

E-Mail (*):

Web Site:

Title (*):

Comments (*):

Enter the code you see (*)



Quote
"Successful programmers know how to ask questions, and they know how to ask the right question. You can't go forward until that happens. A programmer is a rigorous scientist determined to coax the truth out of the ones and zeros. There's the beauty. My pitch to programmers, which is far more revolutionary than any programming language or operating system can be, is to look for understanding where you find it, work with people you want to work with, and don't waste time with people who won't listen and aren't grounded in the truth." Dave Winer
Twitter History
follow me on Twitter
Blog History