Udostępnij za pośrednictwem


INotifyPropertyChanging and INotifyPropertyChanged in Real Life using Expression Trees

Functional Requirements:

Requirements when we need to achieve any or all of the functionalities listed below

  • Undo/Redo functionality in an Application
  • Any of user interface patterns e.g. MVC, MVP, MVVM etc
  • Other patterns State Pattern, Events, Event handlers etc.

In this blog I’ll discuss the points mentioned below

  • Sample Scenario
  • High level Solution
  • Solution using .Net Framework 2 (and Drawbacks)
  • Solution using .Net Framework 3.5 using Lambda Expressions
  • Summary

 

Sample Scenario:

We need to implement Undo/Redo feature in our application. I have a Barometer class that records pressure. We need to implement achieve Undo/Redo feature for this sample class.

 public class Barometer 
{ 
private double pressure; 

public double Pressure 
{ 
get 
{ 
return pressure; 
} 
set 
{ 
pressure = value; 
} 
} 
}

High Level Solution:

I can implement an Undo stack and Redo stack. Whenever the pressure will change I will maintain the state in the Undo/Redo stacks. For every Undoable/Redoable action there will be an entry in these stack. For this simple scenario one can assume a stack to be implemented as Stack<KeyValuePair<string, string>>, where key will correspond to action and value for the property value in KeyValuePair. On Undoing and Redoing the action I’ll load the state from the stacks and apply on the Barometer object using Stack’s Push and Pop operations.

In order to implement that I will be implementing interfaces listed below

  • INotifyPropertyChanging
  • INotifyPropertyChanged

e.g. Pressure has to be updated from “0” to “60”. In this case we can save “0” as it is the previous state in the Undo stack. Redo stack will be empty as there is no action to Redo. The sequence of events for “Pressure” property will be [Before Update<”0”>] –> Write value to Undo Stack –> [Perform Update<”60”>]

Now if I have to to Undo the action that I performed I’ll copy the state from the Undo stack. Then write the state to the Redo stack so that I can Redo the action and then apply the state on the “Pressure” property.

I can use an Command pattern here and if an command implements IUndoable and IRedoable behavior we can have this framework in place.

In this blog I won’t be going into the details of Command pattern and Undo Redo implementation. I can extend this blog if the need arises as the main focus here is to discuss the high level implementation. I’ll focus on the interfaces INotifyPropertyChanging and INotifyPropertyChanged and their usage in this scenario.

 

Solution using .Net framework 2.0:

To implement this prior to .Net Framework 2.0 the general way is displayed in code snippet below

 public class Barometer : INotifyPropertyChanged, INotifyPropertyChanging 
{ 
private double pressure; 

public double Pressure 
{ 
get 
{ 
return pressure; 
} 
set 
{ 
if (value != pressure) 
{ 
//Maintaining the temporary copy of event to avoid race condition 
PropertyChangingEventHandler propertyChanging = PropertyChanging; 
if (propertyChanging != null) 
{ 
propertyChanging(this, new PropertyChangingEventArgs("Pressure")); 
} 

pressure = value;

//Maintaining the temporary copy of event to avoid race condition 
PropertyChangedEventHandler propertyChanged = PropertyChanged; 
if (propertyChanged != null) 
{ 
propertyChanged(this, new PropertyChangedEventArgs("Pressure")); 
} 
} 
} 
}

#region INotifyPropertyChanged Members 
public event PropertyChangedEventHandler PropertyChanged; 
#endregion 

#region INotifyPropertyChanging Members 
public event PropertyChangingEventHandler PropertyChanging; 
#endregion 
}

In the code snippet above I am maintaining the temporary copy to avoid possibility of a race condition in case the last subscriber unsubscribe after the null check and before the event is raised.

As displayed in Red text we are hard coding the name of the property. Here we have only one property but in real world we had to do the same across all the properties for which we needed to have the same behavior.

The issues with this approach are

Cumbersome as you have to do the same across the application

  • Prone to typing mistakes
  • Any refactoring will break the code

We can achieve the same without these drawbacks in .Net framework 3.5 and above using Lambda expressions.

Solution using .Net framework 3.5:

The Barometer class updated to use Lambda Expression is displayed in code snippet below

 public class Barometer : INotifyPropertyChanged, INotifyPropertyChanging 
{ 
private double pressure; 

public double Pressure 
{ 
get 
{ 
return pressure; 
} 
set 
{ 
//Using Lambda Expressions, Anonymous methods and Extension methods
PropertyChanged.SetPropertyValue(PropertyChanging, value, () => this.Pressure, new  Action<double>(delegate(double newValue) { pressure = newValue; })); 
} 
}

#region INotifyPropertyChanged Members 
public event PropertyChangedEventHandler PropertyChanged; 
#endregion

#region INotifyPropertyChanging Members 
public event PropertyChangingEventHandler PropertyChanging; 
#endregion 
}

The difference with the code snippet in .Net 2.0 version is in the set block in code snippet displayed above.

Using the Lambda expressions i.e. (()=>this.Pressure) along with anonymous methods and Extension methods we can achieve the same in much better way.

I have created one more class in which the Extension method SetPropertyValue is defined. This is a generic class and the functionality of method is explained in the inline comments. This is displayed in code snippet below

 public static class PropertyChangeExtensions 
{ 
public static T SetPropertyValue<T>(this PropertyChangedEventHandler postHandler, 
PropertyChangingEventHandler preHandler, T newValue, Expression<Func<T>> oldValueExpression, 
Action<T> setter)

{ 
//Retrieve the old value 
Func<T> getter = oldValueExpression.Compile(); 
T oldValue = getter(); 

//In case new and old value both are equal to default 
//values for that type or are same return 
if ((Equals(oldValue, default(T)) && Equals(newValue, default(T))) 
|| Equals(oldValue, newValue)) 
{ 
return newValue; 
} 

//Retrieve the property that has changed 
var body = oldValueExpression.Body as System.Linq.Expressions.MemberExpression; 
var propInfo = body.Member as PropertyInfo; 
string propName = body.Member.Name; 
var targetExpression = body.Expression as ConstantExpression; 
object target = targetExpression.Value; 

//Maintaining the temporary copy of event to avoid race condition 
var preHandlerTemp = preHandler; 
//Raise the event before property is changed 
if (preHandlerTemp != null) 
{ 
preHandlerTemp(target, new PropertyChangingEventArgs(propName)); 
} 

//Update the property value 
setter(newValue); 

//Maintaining the temporary copy of event to avoid race condition 
var postHandlerTemp = postHandler; 
//Raise the event after property is changed 
if (postHandlerTemp != null) 
{ 
postHandlerTemp(target, new PropertyChangedEventArgs(propName)); 
}

return newValue;
} 
}

Summary:

Thanks for your patience to read up to this. This can be optimized by avoiding the repeated resolution of the expression tree(happening for every property change event). Implementing some sort of caching strategy will do the trick. Well is this the best solution. I’ll say “Depends”.

Pros:

  • Code should be easily refactored
  • Performance issues if any in this approach are considered to be insignificant
  • Solution can be extendable by using interfaces like ISerializable and saving state in Undo/Redo stacks for only the event i.e. chunk and not the complete state
  • You can control the properties for which you want to implement this kind of behavior by applying custom attributes

Cons:

  • Incase of a object tree in which you are having circular references this approach may or may not work. All depends how the application is designed

For a complex object tree I think one approach is to start with the parent object and then iterate over the child nodes until you reach the leafs. For every command that can be Undoable/Redoable you need to have and unique ID (GUID) and then you can save the state of the application against that when performing push/pop operation to Stack.

References:

More Effective C#: 50 Specific Ways to Improve Your C#

Technorati Tags: .Net Framework 3.5,Lambda Expressions,Expression Trees,INotifyPropertyChanging,INotifyPropertyChanged,Anonymous Methods,Extension Methods