Attention
Instead of this code I strongly recommend to use Stephen's Cleary Nito.CalculatedProperties library: https://github.com/StephenCleary/CalculatedProperties.
Turned out Mr Cleary developed and published his library about the same time as I created my code so I wasn't aware of it. It is far more flexible and elegant than my solution, it's extremely simple and useful yet not so many people know about it.
Introduction
Here is the free and elegant way to propagate changes of dependent properties when dependency changed (including dynamically changeable nested properties).
Background
Most popular and naive implementation of INotifyPropertyChanged
looks something like this:
public decimal TotalPrice
{
get { return UnitPrice * Qty; }
}
public int Qty
{
get { return _qty; }
set
{
_qty = value;
RaisePropertyChanged(() => Qty);
RaisePropertyChanged(() => TotalPrice);
}
}
public decimal UnitPrice
{
get { return _unitPrice; }
set
{
_unitPrice = value;
RaisePropertyChanged(() => UnitPrice);
RaisePropertyChanged(() => TotalPrice);
}
}
It has one well known design flaw: independent properties know their dependencies (Qty
and UnitPrice
in this example know that TotalPrice
depends on them).
It's not a big problem for small apps however in complex view model hierarchies, bookkeeping code grows fast and things get out of control pretty quickly.
Wouldn't it be cool to have something like Microsoft Excel sheet instead? You just type a formula depending on other cells while those cells have no idea what depends on them.
Existing Solutions
This problem has been recognized by the community and number of possible solutions already exist. Why do we need another one? Well, let's check what we already have:
UpdateControls. Very promising and working solution. However it requires to abandon INotifyPropertyChanged
along with good old {Binding}
extensions and converters which is not easy in legacy code. Plus one should have some courage to give it a try on new commercial projects.
Fody (successor of Notify Property Weaver). Here we are stepping into the realm of IL-rewriters. Fody is free, fast and good. Unfortunately, it has a big limitation - namely it analyzes dependencies only in limits of one class, i.e., it won't propagate calculated property if it depends on a property of nested/child view model.
PostSharp. Also IL-rewriting solution, it has the same benefits that Fody plus it perfectly handles nested dependencies. Hooray! Unfortunately INotifyPropertyChanged
implementation is a part of PostSharp Ultimate which is expensive (589 EUR as of 27 May 2014).
This list is not full obviously, there are other possible solutions with other or similar limitations. However, it looks like only PostSharp solves the problem to the full extent and unfortunately, it's not free.
Introducing PropertyChangedPropagator
Long story short, it's better to explain how to use it by example (pay attention to statements in bold):
public int Qty
{
get { return _qty; }
set { Set(ref _qty, value); }
}
public decimal UnitPrice
{
get { return _unitPrice; }
set { Set(ref _unitPrice, value); }
}
public decimal TotalPrice
{
get
{
RaiseMeWhen(this, has => has.Changed(_ => _.UnitPrice, _ => _.Qty));
return UnitPrice * Qty;
}
}
public decimal ExchTotalPrice
{
get
{
RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice, _ => _.ExchangeRate));
RaiseMeWhenDynamic(ExchangeRate, has => has.Changed(_ => _.Rate));
return TotalPrice * ExchangeRate.Rate;
}
}
public ExchangeRateViewModel ExchangeRate
{
get { return _exchangeRate; }
set { Set(ref _exchangeRate, value); }
}
As you can see, the initial example has been turned inside out - Qty
and UnitPrice
properties (and ExchangeRate.Rate
property of nested view model) don't know if there is anything depending on them, they just mind to fire their own notifications. And calculated properties say explicitly "yes, I'm depending on this and that, please raise me when some of those have changed".
How does RaiseMeWhen
know which property to propagate? It uses [CallerMemberName]
attribute:
public void RaiseMeWhen<T>(T dep, Action<IRaiseMeWhenHas<T>> has,
[CallerMemberName] string propertyName = null) ...
Though my solution does violate "don't repeat yourself" principle (as well as initial naive example), it encourages to declare dependencies just where they belong - in getters of dependent properties.
Advanced Details
The trickiest part here is RaiseMeWhenDynamic
. It should be used if some property depends on a property of nested view model and this nested view model itself is a subject to change at runtime. Sounds complicated, but in fact quite simple. In the attached example, we can choose one of ExchangeRateViewModel
nested view models and change Rate
property of currently active object.
A drawback of dynamic dependencies is that if there are several nested dependencies of the same type, then it's required to add some string
hint to distinguish one dynamic dependency from another (fortunately this situation is rare). Consider the pseudo code:
public string CurrentContactName
{
get
{
RaiseMeWhenDynamic(PrimaryContact,
"Primary", has => has.Changed(_ => _.Name));
RaiseMeWhenDynamic(SecondaryContact,
"Secondary", has => has.Changed(_ => _.Name));
return (PrimaryContact ?? SecondaryContact).Name;
}
}
One of undeservedly little-known features of INotifyPropertyChanged
is to fire PropertyChanged
event with string.Empty
property name which means "hey, the whole object changed, refresh all properties". RaiseMeWhen
allows to do just that if invoked from constructor (NOTE: It doesn't work for dynamic dependencies). Another aggressive approach is to raise notification when any property of nested view model changed. The code below demonstrates both features:
public MyViewModel(NestedViewModel nested)
{
NestedVm = nested;
RaiseMeWhen(NestedVm, has => has.ChangedAny());
}
Usage of PropertyChangePropagator
might look like declarative code but there is no IL rewriting and the implementation is perfectly imperative. Which means that all propagation subscriptions are lazily created (or dynamically modified in case of dynamic subscriptions) only if getter of dependent property is invoked. In human words - if nobody reads the calculated property, then it won't be propagated. This "feature-like bug" is usually not a problem in real life (after all, you declare properties for views to read them), but makes unit testing a bit more tricky. :-) Check out the source code for details.
The implementation is also thread-safe and uses weak subscriptions, so it's safe to declare dependencies on long-living publishers as subscribers will be eligible for garbage collection when they go out of scope. There is also an implementation trick to optimize memory usage.
I highly recommend to download and run the source code to see more details.
Performance
The solution includes performance tests which gives 1.5-2x times slower results than naive implementation. Analogue in PostSharp is 9 times slower on my machine, I didn't include it in the attached code though because of license dependency. In general, performance will depend on actual complexity of your view models' hierarchy.
Subscription callbacks created lazily on first getter invocation and then cached. In case of dynamic dependencies, subscriptions will be recreated on subsequent read after reference to the dynamic child view model changed.
Lambda expression trees to specify dependencies are themselves part of "has
" callback's body and therefore constructed only once during subscription:
RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice, _ => _.ExchangeRate));
Propagation handlers made static
to optimize memory footprint, i.e., same instances of generated methods are used for multiple view models of the same type. See StaticPropertyChangedHandlersCache class for implementation details.
How to Add This to My Project?
Easy! Copy PropertyChangedPropagator.cs and WeakEventListener.cs and check BindableBaseEx.cs to get the idea how to include RaiseMeWhen*
methods in your base view model class.
It works equally well in WPF and WinRT (didn't try Silverlight, but must be good too).
Possible Improvements
It would be good to add support of ObservableCollection<T>
and to find some way to work with multiple dynamic dependencies of the same type without string
hints.
Summary
PropertyChangedPropagator
can be used as a free and easy solution to propagate property dependencies. It works nicely with legacy code and can be adopted gradually. However if you already have PostSharp Ultimate licence (or spare money for it), then I'd recommend to use it as it's a more mature solution with official support.
History
- 5 June 2014; Added Performance section
- 27 May 2014: Initial version