Examining a slightly smarter way to raise PropertyChanged events

XAML Data Binding - Part 5

Last time, we looked at how to raise a PropertyChanged event, like this:

private void RaisePropertyChanged(string propertyName)
{
    var handler = PropertyChanged;
    if (handler == null)
        return;

    handler(this, new PropertyChangedEventArgs(propertyName));
}

which is fine, but what about that magic string in the signature? We can certainly call this in the property's setter like RaisePropertyChanged("FirstName"), but what happens when the FirstName property gets renamed Forename? It's easy to forget to keep strings up to date, especially if there are plenty of them...

The scene is set for an alternative solution: Enter - Expressions.

An Expression is something that 'represents' a piece of code - it holds the code so that we can use it later if we need to. This allows us to access the information about the property, for example the Name which we need to pass into the PropertyChanged event.

So how do we get the Name out of the property? From the property setter we need to represent the property as a lambda expression, and pass that into a method that takes the lambda in as a parameter and uses it to extract the name. It looks like this:

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


protected static string RaisePropertyChanged<TProp>(this Expression<Func<TProp>> propertyExpression)
{
    var handler = PropertyChanged;
    // Check the event handler exists
    if (handler == null) return;

    var lambda = propertyExpression as LambdaExpression;

    MemberExpression memberExpression;

    // If the property is a value type, the Body will be a Unary expression
    if (lambda.Body is UnaryExpression)
    {
        var unaryExpression = lambda.Body as UnaryExpression;
        memberExpression = unaryExpression.Operand as MemberExpression;
    }
    else
    {
        memberExpression = lambda.Body as MemberExpression;
    }

    if (memberExpression == null)
       throw new ArgumentException("propertyExpression does not represent a valid MemberExpression");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("propertyExpression does not represent a valid public property");

    // Raise the event by invoking the handler
    handler(this, new PropertyChangedEventArgs(propInfo.Name));
}

And that's that. Now we have swapped

RaisePropertyChanged("FirstName");

for

RaisePropertyChanged(() => FirstName);

which is a good deal more refactor-proof than the magic-string version...

binding-series inpc expressions
Posted by: Ian Randall
Last revised: 17 Jun, 2011 09:27 a.m.

Comments

18 Jun, 2011 10:42 p.m.

Update: This post is showing you how to raise a PropertyChanged event from your property setter.

This article from Brendan explains why you might not want to...

Your Comments

Used for your gravatar. Not required. Will not be public.
Posting code? Indent it by four spaces to make it look nice. Learn more about Markdown.

Preview