Sunday, May 23, 2010

INotifyPropertyChanged without the magic strings. A neat alternative....

Source Code: http://www.box.net/shared/5kjn0tr90c

A while back I blogged about the INotifyPropertyChanged interface, and how to properly use it. Back in that post I noted at the end that using magic strings to specify which property has changed, has its drawbacks. A little refresher as to what I'm talking about; this is the common way that the INotifyPropertyChanged interface is used:



public class MyClass : INotifyPropertyChanged
{
private int myInt;

public int MyInt
{
get
{
return this.myInt;
}
set
{
if (this.myInt != value)
{
this.myInt = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MyInt"));
}
}
}
}

public event PropertyChangedEventHandler PropertyChanged;
}


while this does work, the problem is immediately obvious. Actually hard-coding the "MyInt" when the event gets raised is very fragile and extremely error prone. If someone changes the property name for example, and forgets to update the "magic string" in the event-raising code, all your binding goes straight to hell.

Since joining Lab49 I've been using WPF a lot, and the INotifyPropertyChanged interface is used all throughout WPF. Therefore, I've started using a new approach, and while it does require more plumbing code, I think it adds a lot of value since we get rid of the magic strings, and get compile time checking. Here's what I came up with:



public class NotifyProperty<TContaining, TProperty>
{
#region Members

private string propertyName;
private TProperty backingStore;
private Comparison<TProperty> comparer;

#endregion

#region Constructors

public NotifyProperty(Expression<Func<TContaining, TProperty>> expression)
: this(expression, Comparer<TProperty>.Default.ToComparison())
{

}

public NotifyProperty(Expression<Func<TContaining, TProperty>> expression, IComparer<TProperty> comparer)
: this(expression, comparer.ToComparison())
{

}

public NotifyProperty(Expression<Func<TContaining, TProperty>> expression, Comparison<TProperty> comparer)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}

if (comparer == null)
{
comparer = Comparer<TProperty>.Default.ToComparison();
}

this.comparer = comparer;

var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
{
throw new InvalidOperationException("Only properties can be used.");
}

this.propertyName = memberExpression.Member.Name;
}

#endregion

#region Methods

public TProperty GetValue()
{
return this.backingStore;
}

public void SetValue(TContaining containing, TProperty value, PropertyChangedEventHandler propertyChanged)
{
if (this.IsNewValue(value))
{
this.backingStore = value;
this.RaisePropertyChanged(containing, propertyChanged);
}
}

private void RaisePropertyChanged(TContaining containing, PropertyChangedEventHandler propertyChanged)
{
if (propertyChanged != null)
{
propertyChanged(containing, new PropertyChangedEventArgs(this.propertyName));
}
}

private bool IsNewValue(TProperty newValue)
{
if (this.comparer == null) { return true; }

return this.comparer(this.backingStore, newValue) != 0;
}

#endregion
}


And this is how you would use it:



public class MyClass : INotifyPropertyChanged
{
private NotifyProperty<MyClass,int> myInt;

public MyClass()
{
this.myInt = new NotifyProperty<MyClass, int>(m => m.MyInt);
}

public int MyInt
{
get
{
return this.myInt.GetValue();
}
set
{
this.myInt.SetValue(this, value, this.PropertyChanged);
}
}

public event PropertyChangedEventHandler PropertyChanged;
}


Using Expressions, we can take advantage of the fact that we can pass a Func to the constructor of our NotifyProperty class, and still get compile time checking.

I'll briefly run through the code and explain what's happening. The first and most important part to understand is the Expression stuff. While I'm no expert on Expressions, I've used them in the past for this purpose. The compiler allows you to pass a Func wherever an Expression of type Func is expected. Under the covers, the compiler converts it from a Func to an Expression, but at the same time, it extracts some important Metadata about the Func, including the name, which we can then grab through the Name property.

The other piece here is all the Comparison stuff. This isn't really needed, but it just follows the same pattern used when raising the PropertyChanged event. You only want to raise the event if the underlying value actually changed. Therefore, I fleshed it out and overloaded the constructors to allow for different methods of comparison to be passed in. To make that possible, I added this extension method:



public static Comparison<T> ToComparison<T>(this IComparer<T> comparer)
{
return new Comparison<T>(comparer.Compare);
}


This just converts an instance of IComparer<T> to an instance of Comparison so that you can either pass in an interface implementation or a delegate.

Now, like I said earlier, this does require more plumbing code and writing out a property does look more cumbersome. I'll admit that it's a tradeoff, but I personally try to avoid magic strings like the plague, which is why I think this is in fact a much better approach. I added a link to the top of the page with the source code. It contains the class along with some unit tests. Drop me a line and let me know what you think. Until then, Good luck and Happy coding!

2 comments:

Nabamita said...

hy nice tutorial...
I am doing something similar..how do it find the solr home directory through a program??

Google Sucks said...

Very nicely done, thanks.