The PDFx is a lightweight open source .NET library that allows developers to describe dependencies between Properties in declarative C# code. Once relationships are registered, the framework monitors property changes and ensures that the INotifyPropertyChanged.PropertyChanged event is fired for all directly and indirectly dependent properties in a very efficient way. The library is available for WPF, Silverlight, WinRT (Windows Store) and Windows Phone.
I’ve developed the PDFx as an InterKnowlogy RECESS project and published the source code and examples on codeplex.
In a series of blog posts I am going to cover the library’s most important features:
- Part I: Introduction
- Part II: Library Versions
- Part III: Getting Started
- Part IV: Using PDFx with an existing 3rd party framework
- Part V: Simple Property Dependencies
- Part VI: External Property Dependencies
- Part VII: Dynamic External Property Dependencies
- Part VIII: Collection Dependencies
- Part IX: Dynamic Collection Dependencies
- Part X: Caching
- Part XI: Smart Property Changed Notification
- Part XII: Callbacks
- Part XIII: Sanity Checks
- Part XIV: Data Delegation
- Part XV: Two Way Converters
- Part XVI: One Way Converters
Caching
The PDFx allows you to cache Property evaluations and thereby avoid unnecessary executions of the very same evaluation business logic while the underlying data stayed the same.
When the INotifyPropertyChanged.PropertyChanged event is fired for a property, its cached value gets invalidated because the PDFx assumes that the underlying data has changed.
If a cached property depends on other properties, the cached value consequently gets invalidated as soon as any of its direct or indirect source properties change.
This feature helps to save precious CPU time when property evaluations become costly.
The following example demonstrates the usage:
class CacheDemonstration : BindableExt { private AnyType _property1; public AnyType Property1 { get { return _property1; } set { _property1 = value; NotifyPropertyChanged(() => Property1); } } private AnyType _property2; public AnyType Property2 { get { return _property2; } set { _property2 = value; NotifyPropertyChanged(() => Property2); } } public AnyOtherType DependentProperty { get { Property(() => DependentProperty) .Depends(p => p.On(() => Property1) .AndOn(() => Property2)); return CachedValue(() => DependentProperty, () => { return Property1 + Property2; }); } } }
To use the caching feature, simply wrap the Property’s evaluation logic in a call to the Method CachedValue. The first parameter points to the current property while the second parameter points to a delegate that evaluates the property’s business logic.
When DependentProperty is evaluated for the first time, it calculates the property’s value and caches it. Whenever the property is evaluated the next time, it returns the cached value without re-evaluating Property1 or Property2.
As soon as Property1 or Property2 change, however, DependentProperty’s cached value is invalidated and upon its next access re-evaluated and cached again.
Simple Example
Let’s consider the following object graph:
Green circles stand for Input Properties while purple circles indicate calculated properties. The arrows show the underlying math operations as well as the property dependencies.
Such a graph can easily be implemented using the PDFx:
class SimpleCachingExample : BindableExt { public int A1 { get { Property(() => A1) .Depends(p => p.On(() => B1)); return B1; } } public int A2 { get { Property(() => A2) .Depends(p => p.On(() => B1)); return 3 * B1; } } public int B1 { get { Property(() => B1) .Depends(p => p.On(() => C1) .AndOn(() => C2)); return CachedValue(() => B1, () => { return C1 + C2; }); //Note the usage of CachedValue } } private int _c1; public int C1 { get { return _c1; } set { _c1 = value; NotifyPropertyChanged(() => C1); } } private int _c2; public int C2 { get { return _c2; } set { _c2 = value; NotifyPropertyChanged(() => C2); } } }
A subsequent evaluation of A1 and A2 will result in only one evaluation of B1‘s business logic. Any future request of B1 will return the cached value. As soon as C1 or C2 change, B1‘s cached value gets invalidated and reevaluated when its accessed the next time.
Performance Demonstration
The source code of this example can be found in ViewModel CachingDemonstrationVM which is part of the WPFSample’s source code.
Let’s consider the following object graph:
The indicator next to the calculator icon shows the number of Property Evaluations since any Input Property (E1 through E6) has last changed.
A change of E1 with caching enabled results in the following:
Only D1, C1, B1 and A1 get re-evaluated. This makes perfect sense, since those are the only properties that are directly or indirectly dependent on E1.
Without caching, however, a change of E2 results in the following:
The PDFx notices that D1, C1, B1 and A1 all need to get reevaluated and fires INotifyPropertyChanged.PropertyChanged for all of them. The UI layer consequently accesses all of them and causes a reevaluation.
A reevaluation of for example A1 will now access – amongst others –B1. Consequently, B1 is unnecessarily evaluated again (first time it was evaluated by the UI layer). And the game goes on for all the other properties and has a larger performance impact the more intricate your object graph becomes.
The ViewModel for the example above can be implemented as follows:
class CachingDemonstrationVM : BindableExt { public int A1 { get { Property(() => A1) .Depends(p => p.On(() => B2) .AndOn(() => B1)); return CachedValue(() => A1, () => B1 + B2); } } public int B1 { get { Property(() => B1) .Depends(p => p.On(() => C1) .AndOn(() => C2)); return CachedValue(() => B1, () => 2 * C1 - C2); } } public int B2 { get { Property(() => B2) .Depends(p => p.On(() => C2) .AndOn(() => C3)); return CachedValue(() => B2, () => -C2 + C3); } } public int C1 { get { Property(() => C1) .Depends(p => p.On(() => D1) .AndOn(() => D2)); return CachedValue(() => C1, () => D1 + D2); } } public int C2 { get { Property(() => C2) .Depends(p => p.On(() => D3) .AndOn(() => D4)); return CachedValue(() => C2, () => 3 * D3 + 3 * D4); } } public int C3 { get { Property(() => C3) .Depends(p => p.On(() => D5) .AndOn(() => D6)); return CachedValue(() => C3, () => D5 + D6); } } public int D1 { get { Property(() => D1) .Depends(p => p.On(() => E1)); return CachedValue(() => D1, () => E1); } } public int D2 { get { Property(() => D2) .Depends(p => p.On(() => E2)); return CachedValue(() => D2, () => E2); } } public int D3 { get { Property(() => D3) .Depends(p => p.On(() => E3)); return CachedValue(() => D3, () => E3); } } public int D4 { get { Property(() => D4) .Depends(p => p.On(() => E4)); return CachedValue(() => D4, () => E4); } } public int D5 { get { Property(() => D5) .Depends(p => p.On(() => E5)); return CachedValue(() => D5, () => E5); } } public int D6 { get { Property(() => D6) .Depends(p => p.On(() => E6)); return CachedValue(() => D6, () => E6); } } private int _e1; public int E1 { get { return _e1; } set { _e1 = value; NotifyPropertyChanged(() => E1); } } private int _e2; public int E2 { get { return _e2; } set { _e2 = value; NotifyPropertyChanged(() => E2); } } private int _e3; public int E3 { get { return _e3; } set { _e3 = value; NotifyPropertyChanged(() => E3); } } private int _e4; public int E4 { get { return _e4; } set { _e4 = value; NotifyPropertyChanged(() => E4); } } private int _e5; public int E5 { get { return _e5; } set { _e5 = value; NotifyPropertyChanged(() => E5); } } private int _e6; public int E6 { get { return _e6; } set { _e6 = value; NotifyPropertyChanged(() => E6); } } }
Pingback: PDFx – Property Dependency Framework – Part I, Introduction | //InterKnowlogy/ Blogs
Pingback: PDFx – Property Dependency Framework – Part II, Library Versions | //InterKnowlogy/ Blogs
Pingback: PDFX: First Thoughts | //InterKnowlogy/ Blogs