Raising PropertyChanged events with the help of a LINQ expression

Posted on

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.

« Back to home