Jul222009

How to use INotifyPropertyChanged, the type-safe way (no magic string)

Published by eric at 10:43 PM under .NET Framework | C# | Windows | WPF

Implementation of the INotifyPropertyChanged interface is quite simple. There is only one event to implement. Take for example the following simple model class:

public class Model : INotifyPropertyChanged
{
    private string _data;

    public string Data
    {
        get { return _data; }
        set
        {
            if (_data == value)
                return;

            _data = value;

            // Type un-safe PropertyChanged raise 
            PropertyChanged(this, new PropertyChangedEventArgs("Data"));
        }
    }

    #region Implementation of INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged = null;

    #endregion
}

This is a pretty standard way to implement a bindable property. The problem here is the “Data” string to specify which property changed. If someone change the name of the property without changing the content of the string, the code will compile fine but won’t work. In a big application with may properties it can be hard to detect and find the problem.

The best solution is to rely on the compiler to warn us. But because the property name is a string it can’t. So let’s change that line with a type-safe one.

public class Model : INotifyPropertyChanged
{
    private string _data;

    public string Data
    {
        get { return _data; }
        set
        {
            if (_data == value)
                return;

            _data = value;

            // Type safe PropertyChanged raise
            PropertyChanged.Raise(() => Data);
        }
    }

    #region Implementation of INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged = null;

    #endregion
}

What is the trick? Raise is an extension method that takes a lambda expression to specify the name of the property in a type safe way. The Raise method resolve this expression to extract the name of the property and pass it to the PropertyChanged event.

public static class PropertyChangedExtensions
{
    public static void Raise(this PropertyChangedEventHandler handler, Expression<Func<object>> propertyExpression)
    {
        if (handler != null)
        {
            // Retreive lambda body
            var body = propertyExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("'propertyExpression' should be a member expression");

            // Extract the right part (after "=>")
            var vmExpression = body.Expression as ConstantExpression;
            if (vmExpression == null)
                throw new ArgumentException("'propertyExpression' body should be a constant expression");

            // Create a reference to the calling object to pass it as the sender
            LambdaExpression vmlambda = Expression.Lambda(vmExpression);
            Delegate vmFunc = vmlambda.Compile();
            object vm = vmFunc.DynamicInvoke();

            // Extract the name of the property to raise a change on
            string propertyName = body.Member.Name;
            var e = new PropertyChangedEventArgs(propertyName);
            handler(vm, e);
        }
    }
}

All you have to do is to put this extension method in your code and the jib is done. Of course at the end a string will be used to raise the PropertyChanged event but because you don’t have to type it, you don’t have to maintain it.



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags: , , ,

E-mail | Permalink | Trackback | Post RSSRSS comment feed 0 Responses

Comments

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
Loading