Thursday, March 18, 2010

How to use strongly-typed name with INotifyPropertyChanged

You may already have read my posts about how to use INotifyPropertyChanged in a type-safe way (here and here), but sometime you don’t want to modify all your classes to use this method. All you want is avoid the use of a magic string to define the property. Your code will be refactoring proof.

For example let say you have this property:

public string FirstName
{
    get { return _firstName; }
    set 
    {
        if (_firstName == value)
            return;
        _firstName = value;
        RaisePropertyChanged("FirstName");
    }
}

Some refactoring tool, like Resharper, will be able to change the “FirstName” string but not Visual Studio itself. The solution? Replace this string with a strong type value. How? Let’s assume we can do this:

public string FirstName
{
    get { return _firstName; }
    set 
    {
        if (_firstName == value)
            return;
        _firstName = value;
        RaisePropertyChanged(this.NameOf(p => p.FirstName));
    }
}

Notice that you must specify the “this” keyword to make it work. That is exactly What this extension method let you do:

public static class ObjectExtensions
{
    public static string NameOf<T>(this T target, Expression<Func<T, object>> propertyExpression)
    {
        MemberExpression body = null;
        if (propertyExpression.Body is UnaryExpression)
        {
            var unary = propertyExpression.Body as UnaryExpression;
            if (unary.Operand is MemberExpression)
                body = unary.Operand as MemberExpression;
        }
        else if (propertyExpression.Body is MemberExpression)
        {
            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;

        // Extract the name of the property to raise a change on
        return body.Member.Name;
    }
}

You can even use it in your property changed handler:

private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
    if (args.PropertyName == this.NameOf(p => p.FirstName))
    {
        // ...
    }
}

Enjoy!

2 comments:

BEM said...

This is excellent stuff! Thanks for sharing.

Rabin said...

protected void Notify(T obj, Expression> selector)
{
if (propertyChangedEvent != null)
{
MemberExpression memberExpression = selector.Body as MemberExpression;
if (memberExpression == null)
{
UnaryExpression unary = selector.Body as UnaryExpression;
if (unary != null)
{
memberExpression = unary.Operand as MemberExpression;
}
}

if (memberExpression == null)
throw new ArgumentException("member should be of MemberExpression type", "selector");


propertyChangedEvent(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
}
}
Then use

Notify(this, o=> o.PropertyName);