Click here to Skip to main content
12,251,445 members (58,530 online)
Click here to Skip to main content
Add your own
alternative version

Stats

16.6K views
8 bookmarked
Posted

INotifyPropertyChanging and INotifyPropertyChanged in Real Life using Expression Trees

, 23 Jul 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
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 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) 
{ 
//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 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 
{ 
//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 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)

{ 
//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 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

atverma
Software Developer (Senior)
India India
Atul works at Microsoft as a .NET consultant. As a consultant his job is to design, develop and deploy enterprise level secure and scalable solutions using Microsoft Technologies.

His technical expertise include .NET Framework(4.0, 3.5, 3.0), WPF, WCF, SharePoint 2010, ASP.net, AJAX, Web Services, Enterprise Applications, SQL Server 2008, Open Xml, MS Word Automation.

Follow him on twitter @verma_atul

He blogs at
http://www.atulverma.com
http://blogs.msdn.com/b/atverma

You may also be interested in...

Comments and Discussions

 
GeneralAlternative available Pin
nonexisto6-Jan-11 22:04
membernonexisto6-Jan-11 22:04 
GeneralMy vote of 3 Pin
Günther M. FOIDL1-Aug-10 13:21
memberGünther M. FOIDL1-Aug-10 13:21 
GeneralRe: My vote of 3 Pin
atverma2-Aug-10 5:37
memberatverma2-Aug-10 5:37 
GeneralRe: My vote of 3 Pin
Günther M. FOIDL2-Aug-10 9:24
memberGünther M. FOIDL2-Aug-10 9:24 
GeneralRe: My vote of 3 Pin
atverma2-Aug-10 21:01
memberatverma2-Aug-10 21:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160426.1 | Last Updated 24 Jul 2010
Article Copyright 2010 by atverma
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid