CodeProject
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 stacks. 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 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 the code snippet below:
public class Barometer : INotifyPropertyChanged, INotifyPropertyChanging
{
private double pressure;
public double Pressure
{
get
{
return pressure;
}
set
{
if (value != pressure)
{
PropertyChangingEventHandler propertyChanging = PropertyChanging;
if (propertyChanging != null)
{
propertyChanging(this, new PropertyChangingEventArgs("Pressure"));
}
pressure = value;
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 unsubscribes 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 the 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 the code snippet below:
public class Barometer : INotifyPropertyChanged, INotifyPropertyChanging
{
private double pressure;
public double Pressure
{
get
{
return pressure;
}
set
{
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 the 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 a 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 the 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)
{
Func<T> getter = oldValueExpression.Compile();
T oldValue = getter();
if ((Equals(oldValue, default(T)) && Equals(newValue, default(T)))
|| Equals(oldValue, newValue))
{
return newValue;
}
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;
var preHandlerTemp = preHandler;
if (preHandlerTemp != null)
{
preHandlerTemp(target, new PropertyChangingEventArgs(propName));
}
setter(newValue);
var postHandlerTemp = postHandler;
if (postHandlerTemp != null)
{
postHandlerTemp(target, new PropertyChangedEventArgs(propName));
}
return newValue;
}
}
Summary
Thanks for your patience to read up to this point. 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, “It 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
- In case of a object tree in which you are having circular references, this approach may or may not work. All depends on 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 leaves. For every command that can be Undoable/Redoable, you need to have a unique ID (GUID) and then you can save the state of the application against that when performing push/pop operation to Stack.
References