Development Raising PropertyChanged events with the help of a LINQ expression

If you have done any modern GUI development on .NET, then you are probably familiar with the INotifyPropertyChanged interface and the joys of implementing that interface.

A key challenge of implementing INotifyPropertyChanged is that the PropertyChanged event expects you to pass the name of the property that has changed as a string. Keeping strings of property names in your code is a maintenance time bomb – sooner or later, you rename the property and because IntelliSense is only so smart, there is a good chance that you forget to update the string.

Thankfully, it is often possible to use the CallerMemberName and a helper method to let the compiler derive that string automatically for you:

public event PropertyChangedEventHandler PropertyChanged;

public void RaisePropertyChange([CallerMemberName] string propertyName = null)
{
  PropertyChanged?.Invoke(this, args);
}

With that helper method in place, you can write a property like so:

public bool IsFooEnabled
{
  get => this.isFooEnabled;
  set
  {
    this.isFooEnabled = value;
    RaisePropertyChange();
  }
}

Although this idiom is useful (and common), it only works for raising an event for the property that you are currently changing. If you need to raise an event for a different property, such as a derived property, then you still have to pass the property name as string:

RaisePropertyChange("DerivedProperty");

Clearly, it would be nicer and more robust to rewrite this as:

RaisePropertyChange((FooViewModel m) => m.DerivedProperty);

Thanks to LINQ expressions, adding a helper method that lets you do that only takes a few lines of code:

protected void RaisePropertyChange<TModel, TProperty>(
  Expression<Func<TModel, TProperty>> modelProperty)
{
  Debug.Assert(modelProperty.NodeType == ExpressionType.Lambda);
  if (modelProperty.Body is MemberExpression memberExpression &&
      memberExpression.Member is PropertyInfo propertyInfo)
  {
    RaisePropertyChange(propertyInfo.Name);
  }
  else
  {
    throw new ArgumentException("Expression does not resolve to a property");
  }
}

Needless to say, this approach incurs a slight performance hit – but getting rid of property strings is usually well worth that overhead.

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home