Coding, Tips

Tip: Modern INotifyPropertyChanged

Photo Credit: Sanne Roemen

I don’t get much chance to work on the desktop or in Silverlight so I haven’t had too many opportunities to work with the INotifyPropertyChanged interface.  While refactoring an old class toward the Single Responsibility Principle today, I thought to myself “I need an event here,” and since the event was related to a property changing I decided to implement INotifyPropertyChanged instead of using a basic event.

Being rusty with INotifyPropertyChanged I used Google to search for any cool tricks people have come up with since the last time I gave this interface any thought.  I think the most cutting edge way to do it is probably with AOP, but there are some other cool tricks too.  Maybe this is old hat, but it’s new to me.

Generic Setter

There are a few posts out there about using generic setters.  Some of them smell bad.  This one smells good to me: INotifyPropertyChanged, the Anders Hejlsberg Way, by Dan Rigby.  Tips from Anders are always nice🙂.

Here is what the setter looks like:

private void SetProperty<T>(ref T field, T value, string name)
{
    if (!EqualityComparer<T>.Default.Equals(field, value))
    {
        field = value;
        var handler = PropertyChanged;
        if (handler != null)
        {
          handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

The only issue I have with the implementation is that it still requires a magic string.  We’d like to avoid magic strings because they aren’t code, and because they aren’t code they are usually ignored by refactoring tools and can’t be checked by the compiler.

No Magic

Several posts explain how to get rid of the magic string using a lambda expression.  I used this article as reference: Silverlight/WPF: Implementing PropertyChanged with Expression Tree, by Michael Sync.  Michael’s post takes a different approach and just passes the lambda into the setter.  Later he shows some extension methods that can eliminate the need to specify the type parameter explicitly.  But, when I combined his technique with Dan’s I didn’t find the need to specify the type parameter, since the compiler can infer this information from the field and value parameters.

Here’s what we get after combining the two:

private void SetProperty<T>(ref T field, T value, Expression<Func<T>> member)
{
    Contract.Requires(member != null, "member must not be null.");
    var me = member.Body as MemberExpression;
    if (me == null)
    {
        throw new InvalidOperationException("member.Body must be a MemberExpression");
    }

    if (!EqualityComparer<T>.Default.Equals(field, value))
    {
        field = value;
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(me.Member.Name));
        }
    }
}

Here is how we use it:

public int Threshold
{
    get
    {
        return this.threshold;
    }
    private set
    {
        this.SetProperty(ref this.threshold, value, () => this.Threshold);
    }
}

Its no longer refactoring resistant, yay!  I think the error-checking in the setter is a little ugly though.

Cleaner Code with .NET 4.5

I actually found this article first: INotifyPropertyChanged, The .NET 4.5 Way, by Dan Rigby, then followed a link there to the Anders-inspired example.  In the post, Dan shows how to use the CallerMemberNameAttribute (new in .NET 4.5) to eliminate the need for the expression tree.  Without the expression tree, we don’t need the error checking code related to the tree and we can implement a cleaner version of SetProperty.  Be sure to check out Dan’s article as he mentions a couple more useful attributes coming in .NET 4.5.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s