Safe implementation of INotifyPropertyChanged interface
By andre
With arrival of WPF the usage of
INotifyPropertyChanged
interface has expanded dramatically. The most annoying and dangerous part of implementing the interface is, obviously the fact, that you have to pass your option names as strings. This leaves your code prone to bugs - if in 3 months time you rename one of the properties, there is very good chance that you will forget to update the string name as well. In order to overcome the problem we can use some reflection and a little bit of boiler plate code. The good people of the Internet have done all the work for us already, so just copy, paste and enjoy!
Additional advantages of the technique, presented below is better performance, since the parameter objects for property changed events are allocated statically only once. The only drawback is slight increase in the size of static payload size of the class, but not the size of an individual instance. The manual effort you need to put into generating the code cannot be ignored, but is not that large. Aspect oriented programming, such as PostSharp might be a solution to this problem, but it is beyond the scope of this article.
The code below is based on this discussion
First of all add the class below to your project:
public static class TypeManager
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
public static PropertyInfo GetProperty<TType>(Expression<Func<TType, object>> propertySelector)
{
Expression expression = propertySelector.Body;
// If the Property returns a ValueType then a Convert is required => Remove it
if (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked)
{
expression = ((UnaryExpression)expression).Operand;
}
// If this isn't a member access expression then the expression isn't valid
MemberExpression memberExpression = expression as MemberExpression;
if (memberExpression == null)
{
ThrowExpressionArgumentException("propertySelector");
}
expression = memberExpression.Expression;
// If the Property returns a ValueType then a Convert is required => Remove it
if (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked)
{
expression = ((UnaryExpression)expression).Operand;
}
// Check if the expression is the parameter itself
if (expression.NodeType != ExpressionType.Parameter)
{
ThrowExpressionArgumentException("propertySelector");
}
// Finally retrieve the MemberInfo
PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo == null)
{
ThrowExpressionArgumentException("propertySelector");
}
return propertyInfo;
}
private static void ThrowExpressionArgumentException(string argumentName)
{
throw new ArgumentException("It's just the simple expression 'x => x.Property' allowed.", argumentName);
}
public static PropertyChangedEventArgs GetPropertyChangedEventArgs<TType>(Expression<Func<TType, object>> propertySelector)
{
return new PropertyChangedEventArgs(GetProperty(propertySelector).Name);
}
}
Next, when you want to implement the INotifyPropertyChanged interface, you should follow this pattern (read this article for the explanation regarding some attributes in the code):
public class Test : INotifyPropertyChanged
{
[NonSerialized]
private static readonly PropertyChangedEventArgs _valuePropertyChangedArgs =
TypeManager.GetPropertyChangedEventArgs<Test>(x => x.Value);
private string _value:
public string Value
{
get
{
return _value;
}
set
{
if (_value != value)
{
_value = value;
OnPropertyChanged(_valuePropertyChangedArgs);
}
}
}
#region INotifyPropertyChanged Members
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void OnPropertyChanged(PropertyChangedEventArgs propertyChangeArgs)
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
{
h(this, propertyChangeArgs);
}
}
}