INotifyPropertyChanged, The .NET 4.6 Way

posted on 12 Sep 2015 | .NET | Windows Store Apps

This article is part of a series:

  1. INotifyPropertyChanged, The Anders Hejlsberg Way
  2. INotifyPropertyChanged, The .NET 4.5 Way
  3. INotifyPropertyChanged, The .NET 4.5 Way - Revisited
  4. INotifyPropertyChanged, The .NET 4.6 Way

With the release of Visual Studio 2015 & .NET 4.6, we have a new version of the C# compiler and along with it a new version of C# [version 6] that includes new language features that we can leverage to improve the implementation of INotifyPropertyChanged in our applications.

Specifically, with the addition of the Null-conditional Operators in C# 6, we can simplify the implementation of our OnPropertyChanged method from:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChangedEventHandler eventHandler = this.PropertyChanged;
    if (eventHandler != null)
    {
        eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

to:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

This works because the new Null-conditional Operators are thread-safe:

Another use for the null-condition member access is invoking delegates in a thread-safe way with much less code. ... The new way is thread-safe because the compiler generates code to evaluate PropertyChanged one time only, keeping the result in a temporary variable.

While the CallerMemberName attribute in our OnPropertyChanged method implementation will handle most cases, when we do need to manually specify the name of a property in C# 6, we no longer need to resort to the use of magic strings or expression trees. Rather, we can now use the new nameof() operator keyword to pass the property name:

OnPropertyChanged(nameof(MyProperty));

These changes have the added bonus of being usable when you're targeting prior versions of .NET as well (though you must have the .NET 4.6 tooling installed). This means we don't have to update our existing applications and libraries to .NET 4.6 just the take advantage of the new language features.

The code in this post, except for the CallerMemberName attribute, works in Visual Studio 2015 when targeting framework versions all the way down to .NET 2.0. The use of the CallerMemberName attribute requires a target of .NET 4.5 or higher.

Using these features, our implementation of the BindableBase class now looks like this:

using System.ComponentModel;
using System.Runtime.CompilerServices;

/// <summary>
///     Implementation of <see cref="INotifyPropertyChanged" /> to simplify models.
/// </summary>
public abstract class BindableBase : INotifyPropertyChanged
{
    /// <summary>
    ///     Multicast event for property change notifications.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Checks if a property already matches a desired value.  Sets the property and
    ///     notifies listeners only when necessary.
    /// </summary>
    /// <typeparam name="T">Type of the property.</typeparam>
    /// <param name="storage">Reference to a property with both getter and setter.</param>
    /// <param name="value">Desired value for the property.</param>
    /// <param name="propertyName">
    ///     Name of the property used to notify listeners.  This
    ///     value is optional and can be provided automatically when invoked from compilers that
    ///     support CallerMemberName.
    /// </param>
    /// <returns>
    ///     True if the value was changed, false if the existing value matched the
    ///     desired value.
    /// </returns>
    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Equals(storage, value))
        {
            return false;
        }

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }

    /// <summary>
    ///     Notifies listeners that a property value has changed.
    /// </summary>
    /// <param name="propertyName">
    ///     Name of the property used to notify listeners.  This
    ///     value is optional and can be provided automatically when invoked from compilers
    ///     that support <see cref="CallerMemberNameAttribute" />.
    /// </param>
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Null-conditional Operators and the nameof() operator are both fantastic new additions. While you're taking advantage of them, don't forget to familiarize yourself with all of the new language features in C# 6.

Bonus: If you wanted to implement BindableBase while targeting .NET 2.0 to .NET 4.0, it would look like this.