ninja star INotifyPropertyChanged with Compile Time Checking

by Michael Ceranski, posted on March 27 2012

Update: An anonymous reader left a great tip! ".NET 4.5 gives you the new Caller Info attributes, which would remove the need to actually pass in the name of the property, if you're raising the notification from withing the property itself." For more details read this blog post.

Once you start doing WPF MVVM development you will quickly grow tired of implementing the INotifyPropertyChanged interface. So in order to preserve my sanity, I create a base class which implements the INotifyPropertyChanged interface. The first version of my class was defined as:

public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

This is about as basic as an implementation of INotifyPropertyChanged can get. To use it, you just implement the class and call RaisePropertyChanged. Here is an example:

public class MyViewModel : NotifyPropertyChangedBase
{
    private string _name;
    public string Name {
      get{ return _name; }
      set{ 
       _name = value;
       RaisePropertyChanged("Name");
      }
    }
}

After using this version of the code for a couple of days I quickly realized that using a lambda to get the property name would be less error prone than using a string. So in version 2 of the code, I added lambda support.

By the way, the ExtractPropertyName method you see below, was copied verbatim from the Prism project. Prism and MVVM Light Toolkit both take care of wrapping the INotifyPropertyChanged interface.  I personally do not use either framework because I feel that they are both too bloated for my purposes. After a bit of contemplation, I ended up building my own MVVM framework, which I have packaged up and placed on my private NuGet server. However, if you are new to .NET or MVVM then I would suggest starting with a framework first. In any case, here is the updated code:

protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
    var propertyName = ExtractPropertyName(propertyExpression);
    RaisePropertyChanged(propertyName);
}

protected string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression) {
    if (propertyExpression == null) {
        throw new ArgumentNullException("propertyExpression");
    }

    var memberExpression = propertyExpression.Body as MemberExpression;
    if (memberExpression == null) {
        throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
    }

    var property = memberExpression.Member as PropertyInfo;
    if (property == null) {
        throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
    }

    if (!property.DeclaringType.IsAssignableFrom(this.GetType())) {
        throw new ArgumentException("The referenced property belongs to a different type.", "propertyExpression");
    }

    var getMethod = property.GetGetMethod(true);
    if (getMethod == null) {
        // this shouldn't happen - the expression would reject the property before reaching this far
        throw new ArgumentException("The referenced property does not have a get method.", "propertyExpression");
    }

    if (getMethod.IsStatic) {
        throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
    }

    return memberExpression.Member.Name;
}        

With the lambda version of RaisePropertyChanged in place, I can now get compile time checking. Compile time checking is great if you have fat fingers or if you do a lot of refactoring. Let’s take a look at our updated ViewModel:

public class MyViewModel : NotifyPropertyChangedBase
{
    private string _name;
    public string Name {
      get{ return _name; }
      set{ 
       _name = value;
       RaisePropertyChanged(() => Name);
      }
    }
}

I also added an overloaded version of the RaisePropertyChanged method so I could raise multiple properties using a single method call. This is useful when you have two of more related properties. 

protected virtual void RaisePropertyChanged(params string[] propertyNames) {
    foreach( var property in propertyNames ) {
        RaisePropertyChanged(property);
    }
} 

Unfortunately, I could not figure out how to make an overloaded method that takes multiple lambdas. Because the type that represents "T" needs to be different for each expression I struggled to find any syntactical sugar that would make it work. To clarify the problem, I made an overloaded version of the method that takes two lambda expressions. You will notice that T and X are different types. T may be an integer and X may be an observable collection. 

protected void RaisePropertyChanged<T,X>(Expression<Func<T>> one, Expression<Func<X>> two ) 
    RaisePropertyChanged(one);            
    RaisePropertyChanged(two);            
}

With this method you could use the following syntax. Of course, with my sample method above you are limited to two parameters only:

RaisePropertyChanged( () => CountryId, () => States );

If you have a solution for making an overloaded method with params and lambdas then please leave a comment.

Moving on...The classic example of two related properties are Country and State. When you change the value of the country drop down, you want the corresponding states or provinces to appear. Here is an example:

public class AddressViewModel : NotifyPropertyChangedBase
{
     private Address Model { get; private set; }
     
     public AddressViewModel( Address model )
     {
         Model = model;
     }

     public int Country
     {
         get { return Model.CountryId; }
         set
         {
             Model.CountryId = value;
             RaisePropertyChanged( () => CountryId);
             RaisePropertyChanged( () => States);
         }
     }

     public ObservableCollection<Country> Countries {
         ...
     }

     public ObservableCollection<State> States
     {
         ...
     } 

     public int StateId
     {
         ...
     }
}

To summarize, if you find yourself implementing INotifyPropertyChanged a lot then I would highly recommend moving that functionality into a base class. In addition, using the lambda version of the RaisePropertyChanged event can provide you with compile time checking which is always a great thing to have!

Tags: ,
blog comments powered by Disqus

About the author

MikeMichael Ceranski is a developer specializing in the .NET stack. I have spent time as a DBA, Web Developer and even a network engineer. Up til now most of my career has revolved around the .NET stack but I have recently taken an interest in microcontrollers which has forced me to get acquainted with lower level languages such as C, and C++.

View my resume

Sponsors