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.