MVVM pain points: Verbose, repetitive, and redundant Property definitions

One of the biggest features of .NET MVVM development is the ease of data-binding between data objects and UI controls. The mechanism that enables that feature is the INotifyPropertyChanged interface, which has a PropertyChanged event.

When an INotifyPropertyChanged implementation (ViewModel) property is data-bound to a XAML control, that control listens to the PropertyChanged event and looks for an event where the property name matches the data binding.

The PropertyChanged event somehow has to be told the name of the Property that has been changed. Usually, there is a RaisePropertyChanged (String propertyName) method defined on the ViewModel, and the Property setter calls it after setting the value:

private String myFoo;
public String Foo
{
    get { return myFoo; }
    set
    {
        myFoo = value;
        RaisePropertyChanged ("Foo");
    }
}

There’s one thing that really bugs me about this pattern; namely, that “Foo” is repeated so many times (5!). This obviously violates the DRY principle: Don’t Repeat Yourself.

Besides the repetition, we also lose the ability to use the one feature that most easily reduces the amount of code – automatic properties:

public String Foo
{ get; set; }

However, the biggest offense is the String "Foo" being passed into RaisePropertyChanged. I hate Magic Strings and you should too. Erroneous Magic Strings don’t cause any compile-time failures – instead, they usually cause hard-to-catch and subtle run-time failures. Also, if you ever rename a property, it’s all too easy to forget to update the magic string.

Luckily, there are several good solutions to address this.

Using lambdas/expressions

We can add an overload for RaisePropertyChanged:

protected void RaisePropertyChanged (Expression<Func<T>> propertyGet)
{
    RaisePropertyChanged (GetPropertyName (propertyGet));
}

Which takes a LINQ expression and is able to get the property name from a Func:

// this is a too-simple version, google for something more robust
protected static String GetPropertyName (Expression<Func<T>> propertyGet)
{
    if (propertyGet.Body.NodeType == ExpressionType.MemberAccess)
    {
        var memberExpr = (MemberExpression) propertyGet.Body;
        return ((PropertyInfo) memberExpr.Member).Name;
    }
    throw new Exception (String.Format ("Expected MemberAccess expression, got {0}", propertyGet.Body.NodeType));
}

Which allows us to change the Property call to RaisePropertyChanged to:

private String myFoo;
public String Foo
{
    get { return myFoo; }
    set
    {
        myFoo = value;
        RaisePropertyChanged (() => Foo);
    }
}

This gets rid of the Magic String and also provides compile-time safety if the property is renamed. The only drawback is that the Expression is reevaluated every time, and there is a small cost involved with that.

Which brings me to my current solution, a parameter attribute introduced in .NET 4.5:

[CallerMemberName]

protected void RaisePropertyChanged ([CallerMemberName] String propertyName = null)
{
    if (String.IsNullOrEmpty (propertyName)) throw new ArgumentNullException ("propertyName");

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

This wondrous attribute tells the compiler to inject the name of the calling member (property/method) as the parameter!

The property can now be written as:

private String myFoo;
public String Foo
{
    get { return myFoo; }
    set
    {
        myFoo = value;
        RaisePropertyChanged (); // <- "Foo" is automagically provided at compile time!
    }
}

This has the benefits of having no performance cost and being completely immune to renaming issues.

Caveats

Unfortunately, [CallerMemberName] is not available in PCLs. Fortunately, there’s a BCL Portability NuGet package!

But even with the magic of [CallerMemberName], the RaisePropertyChanged (Expression<Func<T>>) and GetPropertyName (Expression<Func<T>>) methods are still needed. If a property ever has dependent properties (for instance FirstName -> FullName), it’ll need to provide the lambda expression for the dependent property change notification, for example:

private String myFirstName;
public String FirstName
{
    get { return myFirstName; }
    set
    {
        myFirstName = value;
        RaisePropertyChanged ();
        RaisePropertyChanged (() => FullName);
    }
}

private String myLastName;
public String LastName
{
    get { return myLastName; }
    set
    {
        myLastName = value;
        RaisePropertyChanged ();
        RaisePropertyChanged (() => FullName);
    }
}

public String FullName
{ get { return String.Format ("{0} {1}", FirstName, LastName); } }

This chaining of property notifications is another MVVM pain point and will be covered in the next post 🙂

Also, if implementing a PropertyChangedEventHandler, you’ll want to use GetPropertyName (Expression<Func<T>>) when comparing the property name so you can avoid Magic Strings:

protected void OnPropertyChanged (Object sender, PropertyChangedEventArgs args)
{
    if (args.PropertyName == GetPropertyName (() => Foo))
    {
        Bar();
    }
}

The ideal solution

This is something that I don’t think has been implemented yet, but should be possible with AOP, perhaps.

What I’d really love to see is an attribute that can work on an automatic property:

[RaisesPropertyChanged] // this would be awesome
public String Foo
{ get; set; }

Such that the compiler or the AOP weaver will convert it into the previous solution:

private String myFoo;
public String Foo
{
    get { return myFoo; }
    set
    {
        myFoo = value;
        RaisePropertyChanged ();
    }
}

Now THAT would be a thing of beauty. I guess I should probably start investigating PostSharp and Matthew Grove‘s book, AOP in .NET.

Feedback Welcome

What do you think? Am I on target, or off my rocker? Can you suggest a better solution? I’m always looking for a better way!

Leave a comment or drop me a line on twitter!

One thought on “MVVM pain points: Verbose, repetitive, and redundant Property definitions

  1. Pingback: MVVM pain points | PhilChuang.com

Leave a Reply