Microsoft Dot Net Master

Microsoft Dot Net Master
Microsoft Dot Net Master

Thursday, July 19, 2012

An elegant way to implement INotifyPropertyChanged

An elegant way to implement INotifyPropertyChanged

Introduction

A very common and popular way to synchronize data between the model and the view in WPF is using DataBinding. The value of the model is transferred to the view once, when the binding is initialized. But for every subsequent change, the model must notify the binding to transfer the value again. This is done by implementing the INotifyPropertyChanged interface on the model.
In the setter of every bound property there must be some code to raise the PropertyChanged event, passing the name of the modified property as a string. This has some disadvantages:
  • You cannot use Auto-properties
  • The property name is passed as a string. This brings ugly errors, when you forgot to change the string after a rename.
  • Every property needs extra code

The basic way to do it

 
private string _name;
 
public string Name
{
   get { return _name; }
   set 
   {
      _name = value;
      PropertyChanged("Name");
   }
}
 
private void PropertyChanged(string prop)
{
   if( PropertyChanged != null )
   {
      PropertyChanged(this, new PropertyChangedEventArgs(prop);
   }
}
 
 
 

A more elegant way to do it

The basic implementation requires two lines of code in the setter and the property name is passed as a string, which is not very robust. A more elegant solution is to use the power of expression trees to get the property name out of a lambda expression. The result looks like this:
 
private string _name;
public string Name
{
   get { return _name; }
   set { PropertyChanged.ChangeAndNotify(ref _name, value, () => Name); }
}
 
 
 
public static bool ChangeAndNotify<T>(this PropertyChangedEventHandler handler, 
     ref T field, T value, Expression<Func<T>> memberExpression)
{
    if (memberExpression == null)
    {
        throw new ArgumentNullException("memberExpression");
    }
    var body = memberExpression.Body as MemberExpression;
    if (body == null)
    {
        throw new ArgumentException("Lambda must return a property.");
    }
    if (EqualityComparer<T>.Default.Equals(field, value))
    {
        return false;
    }
 
    var vmExpression = body.Expression as ConstantExpression;
    if (vmExpression != null)
    {
        LambdaExpression lambda = Expression.Lambda(vmExpression);
        Delegate vmFunc = lambda.Compile();
        object sender = vmFunc.DynamicInvoke();
 
        if( handler != null)
        {
            handler(sender, new PropertyChangedEventArgs(body.Member.Name));
        }
    }
 
    field = value;
    return true;
}
 
 
The solution is ispired by the following articles:

1 comment:

  1. This article is an awesome one which gives an entire information about data binding and how to synchronize data .Thanks for sharing it.
    http://www.dapfor.com/en/net-suite/net-grid/features/performance

    ReplyDelete