.net, C#, CLR, WPF

INotifyPropertyChanged revisited

UPDATE, 12th of July 2009: Full source with sample can be downloaded from the following post

A recurring annoyance with me and quite a few other developers, is the way notification of changes from for instance your domain model to the UI should be handled in environments such as WPF or Silverlight.

The environments are heavily relying on the objects implementing INotifyPropertyChanged and hooks up to the event PropertyChanged to be notified about any changes in any properties.

This works out fine, with the exception of we as developers have to plumb in this code in all our objects.
Normally you would write something like :

[code:c#]
public class Employee : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _firstName;
    public string FirstName
    {
       get { return this._firstName; }
       set
       {
          this._firstName = value;
          this.OnPropertyChanged("FirstName");
       }
    }

    private void OnPropertyChanged(string propertyName)
    {
        if( null != this.PropertyChanged )
        {
           this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
[/code]

One can easily see that the above code can become quite boring to write over and over again. A solution could be to put the frequently used bits in a base class for all your objects. But this steals inheritance.

Another thing that bothers me with the whole INotifyPropertyChanged concept is the fact that I will be having my code filled up with literals that will not give any compiler errors if they contain typos, nor will they be subject to any renaming of properties as part of refactoring.

In my search for a better way of doing this I came across quite a few ways of trying to simplify it. The best one I came across involved using Lambda expressions to express the property that was changed and thus fixing the latter problem with renaming/refactoring. But as far as Google took me, I couldn't find anything that didn't involved switching to an extensible language like Boo or similar to really tackle the problem more elegantly. But my quest couldn't end there.

I started playing again with the problem a bit today and came up with a solution that I think is quite elegant. It involves Lambda expressions, extension methods and reflection. My three favourite things in C# and CLR these days. 🙂

Update: 16th of December 2008, thanks to Miguel Madero for pointing out the problem with value types.

[code:c#]
    public static class NotificationExtensions
    {
        public static void Notify(this PropertyChangedEventHandler eventHandler, Expression<Func<object>> expression)
        {
            if( null == eventHandler )
            {
                return;
            }
            var lambda = expression as LambdaExpression;
            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = lambda.Body as UnaryExpression;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }
            else
            {
                memberExpression = lambda.Body as MemberExpression;
            }
            var constantExpression = memberExpression.Expression as ConstantExpression;
            var propertyInfo = memberExpression.Member as PropertyInfo;
            
            foreach (var del in eventHandler.GetInvocationList())
            {
                del.DynamicInvoke(new object[] {constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name)});
            }
        }
   }
[/code]

When having the extension method above within reach, you will get the Notify() extension method for the PropertyChanged event in your class. The usage is then very simple. Lets revisit our Employee class again.
 
[code:c#]

public class Employee : INotifyPropertyChanged

{

    public event PropertyChangedEventHandler PropertyChanged;

    private string _firstName;

    public string FirstName

    {

       get { return this._firstName; }

       set

       {

          this._firstName = value;

          this.PropertyChanged.Notify(()=>this.FirstName);

       }

    }

}

[/code]

This is a highly reusable and pretty compact technique, and if you're not like me and aren't all that agressive with putting "this." all over the place, it will be even more compact. 🙂

Update, 16th of December 2008:

Since my original post, I also added a SubscribeToChange() extension method. The reason for this is pretty much that I literally don't like literals and wanted to have the ability to subscribe to changes for a specific property.

[code:c#]
        public static void SubscribeToChange<T>(this T objectThatNotifies, Expression<Func<object>> expression, PropertyChangedEventHandler<T> handler)
            where T : INotifyPropertyChanged
        {
            objectThatNotifies.PropertyChanged +=
                (s, e) =>
                    {
                        var lambda = expression as LambdaExpression;
                        MemberExpression memberExpression;
                        if (lambda.Body is UnaryExpression)
                        {
                            var unaryExpression = lambda.Body as UnaryExpression;
                            memberExpression = unaryExpression.Operand as MemberExpression;
                        }
                        else
                        {
                            memberExpression = lambda.Body as MemberExpression;
                        }
                        var propertyInfo = memberExpression.Member as PropertyInfo;

                        if(e.PropertyName.Equals(propertyInfo.Name))
                        {
                            handler(objectThatNotifies);
                        }
                    };
        }
[/code]

 

The above code extends classes that implements INotifyPropertyChanged and gives you a syntax like  follows for subscribing to events:

[code:c#]
myObject.SubscripeToChange(()=>myObject.SomeProperty,SomeProperty_Changed);
[/code]

 And then your handler would look like this:

[code:c#]
private void SomeProperty_Changed(MyObject myObject)
{
    /* … implement something here */
}
[/code]
Standard

11 thoughts on “INotifyPropertyChanged revisited

  1. Looks great. The only drawback is that it doesn’t work with value types because the body is an UnaryExpression and not a MemberExpression.
    This is weird, I wouldn’t expect this, but it looks like it tries to convert the boolean to object.

    So the expression looks like
    Convert(value(Property))

    This code will fix it
    if (memberExpression == null)
    {
    var unaryExpression = (lambda.Body as UnaryExpression);
    memberExpression = unaryExpression.Operand as MemberExpression;
    }

  2. Nice. I have been annoyed with the exact same thing, but unlike you, had no clue what to do about it :p

    Added Miguels code (although not sure exactly when it is needed?) and a null check so it doesn’t crash.

    if (eventHandler == null)
    return;

  3. I modified also the code to do other things, specific to our app. Instead of doing it as an extension method, I’m doing it through a BaseClass that has a RaisePropertyChanged method with several overloads. This is the code:

    private SynchronizationContext _syncContext;
    private static PropertyChangedEventArgs GetEventArgs(string propertyName)
    {
    PropertyChangedEventArgs pe = null;
    if (_eventArgsMap.TryGetValue(propertyName, out pe) == false)
    {
    pe = new PropertyChangedEventArgs(propertyName);
    _eventArgsMap[propertyName] = pe;
    }

    return pe;
    }

    protected void RaisePropertyChanged(params Expression<Func<object>>[] expressions)
    {
    if (expressions == null)
    throw new ArgumentNullException("expressions", "You need to provide at least one expression");
    if (expressions.Length <= 0)
    throw new ArgumentOutOfRangeException("expressions", "You need to provide at least one expression");

    string[] propertyNames = GetPropertyNames(expressions);

    RaisePropertyChanged(propertyNames);
    }

    private string[] GetPropertyNames(Expression<Func<object>>[] expressions)
    {
    string[] propertyNames = new string[expressions.Length];
    for (int i = 0; i < expressions.Length; i++)
    {
    var expression = expressions[i];
    var lambda = expression as LambdaExpression;
    var memberExpression = lambda.Body as MemberExpression;
    if (memberExpression == null)
    {
    var unaryExpression = (lambda.Body as UnaryExpression);
    memberExpression = unaryExpression.Operand as MemberExpression;
    }

    var propertyInfo = memberExpression.Member as PropertyInfo;

    propertyNames[i] = propertyInfo.Name;
    }
    return propertyNames;
    }

    protected void RaisePropertyChanged(string propertyName)
    {
    if (String.IsNullOrEmpty(propertyName))
    throw new ArgumentNullException("propertyName");

    if (_propChangedHandler == null)
    return;

    if (_syncContext == null)
    _syncContext = SynchronizationContext.Current;

    _syncContext.Post(delegate(object state)
    {
    RaisePropertyChangedInternal(propertyName);
    }, null);
    }

    protected void RaisePropertyChanged(params string[] propertyNames)
    {
    if ((propertyNames == null) || (propertyNames.Length == 0))
    throw new ArgumentNullException("propertyNames");
    if (_propChangedHandler == null)
    return;

    _syncContext.Post(delegate(object state)
    {
    foreach (string propertyName in propertyNames)
    RaisePropertyChangedInternal(propertyName);
    }, null);
    }
    private void RaisePropertyChangedInternal(string propertyName)
    {
    if (_propChangedHandler != null)
    _propChangedHandler(this, GetEventArgs(propertyName));
    }

    private PropertyChangedEventHandler _propChangedHandler;
    public event PropertyChangedEventHandler PropertyChanged
    {
    add
    {
    _propChangedHandler = (PropertyChangedEventHandler)Delegate.Combine(_propChangedHandler, value);
    }
    remove
    {
    if (_propChangedHandler != null)
    _propChangedHandler = (PropertyChangedEventHandler)Delegate.Remove(_propChangedHandler, value);
    }
    }

  4. Thanks a lot guys for the input and the modifications. I’ve modified the code quite a bit for some oddities in my current project. I’ll merge in your stuff as well.

    Thanks again!

  5. Hi Einar,

    Saw your presentation in Oslo at Microsoft on Tuesday and ended up on your blog that way – good stuff! 🙂

    I like the solution presented here, but I think MS should consider doing better – I am a hobby developer at max, but I think they should consider automatically supporting INotifyPropertyChanged with auto-implemented properties on classes which implement the interface, like I clumsily try to explain here:

    http://slothonline.blogspot.com/2008/08/automatically-implemented-properties.html

    This is probably too simplistic, but I think it would ease the pain of bringing your business objects to WPF/Silverlight. 🙂

  6. Sub-Star says:

    Nice work Einar!

    I also attended the presentation you had on tuesday, and I liked how you and your colleague interacted with each other throughout the session.

    I have now tried to implement your latest changes to this problem with the SubscribeToChange() method (brilliant once again!), and ended up with a compile error:
    The type or namespace name ‘PropertyChangedHandler’ could not be found

    Seems like you forgot this line in the NotificationExtensions class:
    public delegate void PropertyChangedHandler<T>(object sender);

    Now it compiles and appears to work as expected, and I am a very happy programmer! =)
    Thank your for sharing your solution!

    Ps. I agree with you on your choice of background and text color in the Visual Studio editor… 😉

  7. Thanks for your kind words and feedback.
    Sub-Start: Thanks for pointing out my typo.. 🙂 Its now fixed.

    As to Microsoft not supporting or doing the "magic" as I’ve posted out of the box.
    It might be because the technique is considered too much magic. Also, there are speed impacts to doing this. One could of course cache this and make it even more speedier.

    The way I personally think Microsoft should overcome issues like this is to open up the compiler and provide compiler extensibility options. That way the guys writing the framework could just write a new "keyword" called notify that one could decorate the properties that should notify. I’ve blogged about this overe here: http://www.ingebrigtsen.info/post/2008/11/03/Compiler-extensions-is-just-another-framework.aspx

  8. Hey, I took your work and wrapped it into a fluent interface to help me with a side project I was working on. It looks like this when I use it:

    var context = new BindingContext(_MyPerson, () => PropertyChanged);
    context
    .Bind(() => FirstName)
    .From(()=> _MyPerson.FirstName)
    .OnChangeDo((s,e)=>Clicky.RaiseCanExecuteChanged());

    I also posted the code (yours and mine) to github here: http://github.com/jcbozonier/bound.net/tree/master

  9. Pingback: WPF MVVM之INotifyPropertyChanged接口的几种实现方式 | 编程·早晨

Leave a Reply