Behaviour Subject as WPF ViewModel backing field
-
Tuesday, November 06, 2012 1:11 PM
I've been reading Lee Campbell's intro to rx and its been super helpful with getting up to speed with the framework.
In the section about BehaviourSubject, the following is mentioned:
BehaviorSubject<T>s are often associated with class properties. As they always have a value and can provide change notifications, they could be candidates for backing fields to properties.
I wanted to enquire as to whether people are finding this practical and if so how are you using it? I can't see a way of getting to the current value outside of the monad, which means that binding it to a WPF view is going to be tricky without having an extra copy of the value thats populated in a subscribe.
Dan
All Replies
-
Tuesday, November 06, 2012 2:32 PM
Hi Dan,
You can use the First operator to extract a value from a BehaviorSubject<T>.
Alternatively, if you're using Rxx, then consider using the Subscription extension to define a two-way binding backed by subjects. This extension behaves similar to a normal Binding except that it understands the IObservable<T> and IObserver<T> interfaces for communicating changes. Since ISubject<T> implements both of these interfaces, it makes it easy to push new values using a BehaviorSubject<T> while receiving updates from the UI by subscribing to the subject, or if you need to separate concerns, then by creating a different Subject<T> or a custom observer.
Here's a lab illustrating how to perform one-way and one-way-to-source bindings using the Subscription extension:
Here's a new example of how to use a BehaviorSubject<T> to perform a two-way binding. I'll add this lab to the next version of Rxx.
XAML: (Note that BaseLab is just like a normal UserControl. There's nothing special going on here.)
<local:BaseLab x:Class="Rxx.Labs.UI.TwoWaySubscriptionLab" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local="clr-namespace:Rxx.Labs" xmlns:rxx="clr-namespace:System.Windows.Reactive;assembly=Rxx" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400" DataContext="{Binding RelativeSource={RelativeSource Self}}"> <StackPanel VerticalAlignment="Center"> <Label Content="Current value:" /> <TextBox Margin="20 0" Text="{Binding CurrentValue}" IsReadOnly="True" /> <Button Margin="0 20" HorizontalAlignment="Center" Content="Set Current Time" Click="SetTextToCurrentTime" /> <Label Content="Enter text here:" /> <TextBox Margin="20 0" Text="{rxx:Subscription Text, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> </local:BaseLab>Code Behind:
using System; using System.Reactive.Subjects; using System.Windows; namespace Rxx.Labs.UI { public partial class TwoWaySubscriptionLab : BaseLab { public static readonly DependencyProperty CurrentValueProperty = DependencyProperty.Register( "CurrentValue", typeof(string), typeof(TwoWaySubscriptionLab), new PropertyMetadata("Default Value")); // This property is here just to prove that the Subscription binding is working. public string CurrentValue { get { return (string) GetValue(CurrentValueProperty); } set { SetValue(CurrentValueProperty, value); } } public ISubject<string, string> Text { get { return text; } } private readonly BehaviorSubject<string> text = new BehaviorSubject<string>("Default Value"); public TwoWaySubscriptionLab() { InitializeComponent(); // Having an observable for UI input makes it easy to compose complicated queries against several properties. // For example, you could write a query using the CombineLatest operator to be notified when any property // is updated and passes validation. Then you can automatically take action, without requiring the user to // press a button. // Assigning CurrentValue isn't necessary, it simply proves that the Subscription binding is working. text.Subscribe(value => CurrentValue = value); } private void SetTextToCurrentTime(object sender, RoutedEventArgs e) { var time = DateTime.Now.ToLongTimeString(); text.OnNext(time); // We'll keep this in sync; otherwise, it looks strange to the user. CurrentValue = time; } } }Here's another new example of how to use two subjects to perform a two-way binding. I'll add this lab to the next version of Rxx.
XAML: (Same as above)
Code Behind:
using System; using System.Reactive.Subjects; using System.Windows; namespace Rxx.Labs.UI { public partial class TwoWaySubscriptionLab : BaseLab { public static readonly DependencyProperty CurrentValueProperty = DependencyProperty.Register( "CurrentValue", typeof(string), typeof(TwoWaySubscriptionLab), new PropertyMetadata("Default Value")); // This property is here just to prove that the Subscription binding is working. public string CurrentValue { get { return (string) GetValue(CurrentValueProperty); } set { SetValue(CurrentValueProperty, value); } } public ISubject<string, string> Text { get { return Subject.Create<string, string>(textFromUser, text); } } private readonly BehaviorSubject<string> text = new BehaviorSubject<string>("Default Value"); private readonly Subject<string> textFromUser = new Subject<string>(); public TwoWaySubscriptionLab() { InitializeComponent(); // Having an observable for UI input makes it easy to compose complicated queries against several properties. // For example, you could write a query using the CombineLatest operator to be notified when any property // is updated and passes validation. Then you can automatically take action, without requiring the user to // press a button. // // Here, we're just simulating a property with a backing field (text). // Updating the BehaviorSubject<T> is like assiging the value to a backing field of a normal property. textFromUser.Subscribe(value => text.OnNext(value)); // Assigning CurrentValue isn't necessary, it simply proves that the Subscription binding is working. textFromUser.Subscribe(value => CurrentValue = value); } private void SetTextToCurrentTime(object sender, RoutedEventArgs e) { var time = DateTime.Now.ToLongTimeString(); text.OnNext(time); // We'll keep this in sync; otherwise, it looks strange to the user. CurrentValue = time; } } }- Dave
- Edited by Dave Sexton Monday, November 12, 2012 8:25 PM Added single-subject example.
-
Tuesday, November 06, 2012 2:37 PM
Hi,
I just noticed a bug in Subscription that causes the UI to try to call the setter whenever the text is changed by the user, which doesn't exist so an exception is thrown internally and caught by the WPF binding system. This makes the UI feel a bit more sluggish. I hadn't noticed this before because I normally don't use UpdateSourceTrigger=PropertyChanged, though I did in this lab for effect.
I'll take a look at this bug and fix it for the next release of Rxx. Until then, I noticed that running the application without a debugger attached makes a huge difference in the performance of UpdateSourceTrigger=PropertyChanged, so I don't necessarily consider this bug a showstopper though if you're concerned then I recommend using UpdateSourceTrigger=Default instead (or simply don't specify it at all). That's how I've been using the Subscription extension in professional applications and I haven't had any performance problems.
- Dave
-
Tuesday, November 06, 2012 3:14 PM
Thanks Dave, thats a very interesting approach, and not really what I was expecting!
Do you find that you end up with pain keeping the VM state in the land of the monad? I can immediately think that our view persistence logic would have to do a tone of .First() operations to pull out state etc. Having said that I imagine it might simply other things as people can't then step outside of the monad and hack at values very easily.
I've generally used INotifyPropertyChanged on our projects so that our VMs aren't as coupled to WPF, and one of our major concerns is efficiency as we have grids of 20+ columns and a few hundred rows all ticking near real time.
At the moment I've been experimenting with property wrappers that manage the INotifyPropertyChanged and also offer an Observable for the property using a lazy initialised behaviour subject, so my initial question was around how to get the value out of the subject for the get/set methods so I don't have to also have a seperate backing field. Using First() would work, but setting up and tearing down a subscription is quite a lot of extra overhead. Obviously an alternative is to fork BehaviourSubject so that the value is exposed.
However I'm also thinking this wrapping approach is too heavy as we don't need observables for all the props, and even though they are lazy created it is extra weight especialy given I currently have a seperate backing field.
One altertiave is that I could create a helper that returns an observable for a property. It could do this by taking a property expression as a param, which is used to bind to the INotifyPropertyChanged event, filtering by the property name (reflected from the expression), and then uses the expression to grab the current value each time the event fires, so that I can then shoot it into a behaviour subject returned by the helper. This would obviously be leveraging the fact that we only modify the VM in the sync context as otherwise I'd be nervous reaching into the vm for current value.
I have a suspicion this is a similar approach to rx ui, but haven't really dug into the code there.
I guess the main point I'm making here is that we are pretty obsessed about performance, so aren't really happy casually creating extra objects or adding weight to property getting/setting.
-
Tuesday, November 06, 2012 4:03 PM
Hi Dan,
> Do you find that you end up with pain keeping the VM state in the land of the monad? [snip]
No, because typically I'll only use observables. I give no reason to leave the monad in my implementation :)
Personally, I tend to not use INotifyPropertyChanged at all in my view models. As a matter of fact, Rxx provides a ViewModel base class that is specifically designed to support reactive binding scenarios. It also derives from DependencyObject, though I don't use it to define dependency properties on my view models; it's simply there for direct access to the Dispatcher.
Here's the ViewModel lab, showing how to assign a view model to a view using the View.Model attached property:
The idea behind ViewModel is that observable subscriptions are created whenever the view (FrameworkElement) is loaded and then disposed whenever the view is unloaded. It also provides access to the underlying disposables collection via the *Disposable methods, allowing you to add new subscriptions anytime after the view model is attached. The IsInDesignMode property is another useful feature. Furthermore, it provides direct access to the view (sometimes it's necessary :) via the Element property.
Alternatively, if you can't use the ViewModel base class, then you can implement IViewModel yourself on your own base class to get the same behavior; however, the View.Model attached property doesn't require this interface. It will work with any kind of view model object, regardless of its base class, though in order to support all of its features you should implement IViewModel or derive from ViewModel.
> one of our major concerns is efficiency as we have grids of 20+ columns and a few hundred rows all ticking near real time. [snip]
> Using First() would work, but setting up and tearing down a subscription is quite a lot of extra overhead.With all of the performance improvements made to Rx 2.0, I believe that creating a subscription to Subject<T> amounts to the creation of a few ephemeral objects and a couple of virtual method calls, at most, when using the First operator. Using INotifyPropertyChanged often requires reflection with every notification, which can be much slower than the alternatives. I'd suggest actually testing the performance of each if it really matters to you that much.
> However I'm also thinking this wrapping approach is too heavy as we don't need observables for all the props,
> and even though they are lazy created it is extra weight especialy given I currently have a seperate backing field.Consider replacing the backing fields with subjects. As mentioned previously, performance should be fine, but you should test it if you're concerned.
> One altertiave is that I could create a helper that returns an observable for a property.
Perhaps, but that's even more overhead. Now you have a property with a backing field, INotifyPropertyChanged with reflection, and an observable wrapper.
> It could do this by taking a property expression as a param, which is used to bind to the INotifyPropertyChanged event [snip]
If you're considering that approach, Rxx provides the FromPropertyChangedPattern extension.
FromPropertyChangedPattern Lab
- Dave
- Edited by Dave Sexton Tuesday, November 06, 2012 5:05 PM Correction
-
Tuesday, November 06, 2012 4:21 PM
Hi Dan,
I am glad the book is proving helpful. The point in the book is to let you consider using BehaviorSubjects for backing fields. I am not actively encouraging it. I find that I do in a few places do this, but very few places. The norm is to use a normal-old-INotifyPropertyChanged-property and then have extension methods to INotifyPropertyChanged instances to turn Properties into change sequences. I am sure that there are tons of examples of this around the web that do this.
This is one that may get you started.
public static PropertyInfo GetPropertyInfo<TSource, TValue>(this Expression<Func<TSource, TValue>> property) { if (property == null) throw new ArgumentNullException("property"); var body = property.Body as MemberExpression; if (body == null) throw new ArgumentException("Expression is not a property", "property"); var propertyInfo = body.Member as PropertyInfo; if (propertyInfo == null) throw new ArgumentException("Expression is not a property", "property"); return propertyInfo; } /// <summary> /// Return the new values from an <seealso cref="INotifyPropertyChanged"/> <paramref name="source"/> as they change for the given property. /// </summary> /// <typeparam name="T">The type of the source object. Type must implement <seealso cref="INotifyPropertyChanged"/>.</typeparam> /// <typeparam name="TProperty">The type of the property that is being observed.</typeparam> /// <param name="source">The object to observe property changes on.</param> /// <param name="property">An expression that describes which property to observe.</param> /// <returns>Returns an observable sequence of values as the property changes.</returns> /// <example> /// <code><![CDATA[ /// myViewModel.ObserveProperty(vm=>vm.MyProperty) /// ]]></code> /// </example> public static IObservable<TProperty> ObserveProperty<T, TProperty>(this T source, Expression<Func<T, TProperty>> property, bool observeInitialValue = false) where T : INotifyPropertyChanged { var propertyName = property.GetPropertyInfo().Name; var propertySelector = property.Compile(); var observable = from evt in Observable .FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs> (h => (s, e) => h(s, e), h => source.PropertyChanged += h, h => source.PropertyChanged -= h) where evt.EventArgs.PropertyName == propertyName select propertySelector(source); return observeInitialValue ? observable.Merge(Observable.Return(propertySelector(source))) : observable; }
This way you dont need to do .First() and to non-Rx geeks, your ViewModel still looks normal.
HTH
Lee
Lee Campbell http://LeeCampbell.blogspot.com
-
Tuesday, November 06, 2012 4:59 PM
Hi Lee, that looks pretty much like what I was proposing! Thanks again for the book, its made a huge difference for me as I really found it hard to get to grips with rx when so much was undocumented. Makes a lot more sense now.
Dave - as per Lee's example, the reflection would be a 1 time operation when creating the observable, and then just invoking a compiled delegate on each OnNext which is an overhead but not huge.
Have you benchmarked your binding to observable approach vs INotify/DependencyProps? I can see some advantages wrt to modifying the vm from any thread and allowing the binding subscription to marshall to the sync context. Although potentially a risk for death by context switch as it would be very fine grained (at property level) whereas we can batch our context switches at the moment around messages that impact a group of properties.
This does raise questions about how you handle collections though. Do you have a binding for them as they are particularly fragile wrt to modification outside of the sync context? Dave mentioned modelling collections as IObservable<IObservable<TRow>> in his book, and this is something I'm actively considering if I can convince myself its not going to melt our brains in practise.
Having discussed all this at length with a colleague, our current thinking is to create a T4 template to code generate the notifypropchanged and tied observables for our view models. This allows us to ditch all the extra references for wrapper classes (e.g. my Prop<T> wrapper) etc and yet still do things like cache the INotifyPropertyChangedEventArgs on a per view model basis as statics. Given the need for performance, this does feel like the best solution at the moment. We have a lot of grids flying around showing real time data, and whilst everyone is really happy with how it performs right now (we used rx and TPL-df for very broad parallelisation and async on everything) I don't want to be complacent. I'm still interested in your monadic approach as it might lead to a solution we haven't considered.
- Edited by Dan Harman Tuesday, November 06, 2012 5:03 PM
-
Tuesday, November 06, 2012 5:28 PM
Hi Dan,
> Dave - as per Lee's example, the reflection would be a 1 time operation when creating the observable [snip]
Yes, it's similar to the approach that I used in FromPropertyChangedPattern, though I prefer Rxx because it provides an overload that doesn't require you to specify the source object 3 times. :)
> Have you benchmarked your binding to observable approach vs INotify/DependencyProps?
No, I've never had any problems that prompted me to look into this.
> I can see some advantages wrt to modifying the vm from any thread [snip]
Yes, this is an advantage because the Subscription binding does the context switch for you automatically.
> Although potentially a risk for death by context switch as it would be very fine grained (at property level) [snip]
Perhaps, for a notification-heavy application. It's not a requirement to use concurrency anyway. You can batch context switches by adding the ObserveOnDispatcher operator at the appropriate places in your queries, if that's what you need.
> This does raise questions about how you handle collections though. Do you have a binding for them [snip]
IObservable<T> is a sequential collection. Take a look at the subscription lab that I linked before. Notice that the GrowingList property returns IObservable<long> and is bound directly to a ListBox via the Subscription extension. The SlidingList property returns an IListSubject<string>, another Rxx feature, which represents an object that is both a mutable IList<T> and an ISubject<T>. This property is also bound directly to a ListBox via the Subscription extension. The lab illustrates the differences in behavior; namely, that IListSubject<T> is like an IObservable<T> that allows you to remove items.
> Having discussed all this at length with a colleague, our current thinking is to create a T4 template [snip]
I'm not necessarily opposed to this approach myself. I love T4 templates. It sounds like you have a highly performance-critical application, so code generation may be your best solution, though perhaps it would be worth taking the time to benchmark Subscription bindings and Rxx.ViewModel as they may help to simplify your code, especially if your view models have (complex) reactive behaviors invoked by UI events and/or external input.
The key is whether or not writing queries within your view models will be helpful to you. It depends on your requirements. The reactive solution is not meant to be a performance-enhancement, as far as I'm concerned. It's meant to simplify programming of (complex) reactive behaviors within view models. I find that almost every view model I write benefits from having a clean set of queries written in a single Attaching method overload, with automatic subscription maintenance and property change notifications (two way).
- Dave
-
Tuesday, November 06, 2012 5:53 PM
The key is whether or not writing queries within your view models will be helpful to you. It depends on your requirements. The reactive solution is not meant to be a performance-enhancement, as far as I'm concerned. It's meant to simplify programming of (complex) reactive behaviors within view models. I find that almost every view model I write benefits from having a clean set of queries written in a single Attaching method overload, with automatic subscription maintenance and property change notifications (two way).
- Dave
That's pretty much what we are doing too, but we don't always respect the monad at the moment. e.g. its often annoying trying to capture changes on 4 fields of different types with chaining of combinelatest, so easier to project each fields changes to IObservable<Unit> and then we can do a simple merge of these and reach into the VM to perform the calculation.
Its obviously not properly funcitonal, and I feel a little uncomfortable with that, but at the same time its more lightweight and easier than staying in the monad in this type of scenario.
I will definitely have a look at your lab as the ListSubject looks like an interesting approach.
Thanks for all the help chaps.
-
Tuesday, November 06, 2012 6:07 PM
Hi,
> e.g. its often annoying trying to capture changes on 4 fields of different types with chaining of combinelatest
I'm not sure that I understand the problem. Are you tied to an older version or Rx? You don't necessarily need to chain CombineLatest as of Rx 2.0 because of the N-ary overloads, unless I've misunderstood your requirements.
If you'd like to provide a more concrete example we'll come up with a better answer for you. :)
> I will definitely have a look at your lab
You can download Rxx 1.3 Labs for .NET 4.0 and simply run the executable. The source code is embedded. However, note that Rxx 2.0 may have some changes and bug fixes though it hasn't been released yet. Source control is up-to-date as of Rx 2.0.21103, so you can build it yourself if you'd like.
- Dave
-
Tuesday, November 06, 2012 6:24 PM
Hi,
> e.g. its often annoying trying to capture changes on 4 fields of different types with chaining of combinelatest
I'm not sure that I understand the problem. Are you tied to an older version or Rx? You don't necessarily need to chain CombineLatest as of Rx 2.0 because of the N-ary overloads, unless I've misunderstood your requirements.
If you'd like to provide a more concrete example we'll come up with a better answer for you. :)
> I will definitely have a look at your lab
You can download Rxx 1.3 Labs for .NET 4.0 and simply run the executable. The source code is embedded. However, note that Rxx 2.0 may have some changes and bug fixes though it hasn't been released yet. Source control is up-to-date as of Rx 2.0.21103, so you can build it yourself if you'd like.
- Dave
yeah we are on rx 1.x at the moment. We could upgrade but it hits a lot of our framework, and this is a business critical app, so a bit reluctant to get on the bleeding edge of rx again. I remember all the breaking changes in 1.x...
Is 2.x stable and reliable enough to go to now? I've seen a couple of recent threads saying 'my code broke when I grabbed the latest version' which was a bit concerning. Also Lee's book only covers 1.x so if there are radical changes not sure how to get up to speed.
-
Tuesday, November 06, 2012 7:12 PM
Hi Dan,
> yeah we are on rx 1.x at the moment. [snip]
Rxx 1.3 provides the N-ary overloads of CombineLatest and Zip that were missing in Rx 1.0.
> Is 2.x stable and reliable enough to go to now? [snip]
It went through a fairly long beta phase, so perhaps it was safe to have considered it stable and reliable enough upon its first release. The recent bugs that were identified have already been fixed and deployed, I believe; however, they wouldn't have affected everyone anyway. I haven't run into them myself and I've been using Rx 2.0 since beta.
> Also Lee's book only covers 1.x so if there are radical changes not sure how to get up to speed.
Not many public surface area changes. The radical stuff is entirely internal and a lot of it was performance related. The biggest public change that I can think of at the moment is that there are now multiple assemblies that you must reference. Backward compatibility has been close to 100% for my apps switching from Rx 1.x to Rx 2.0.
The details of the changes span a couple of very long blog posts on the Rx Team blog.
- Dave
-
Thursday, November 08, 2012 10:26 AM
Hi Dan,
With Rx hat on
Sorry that the book only targets V1. If I can this holiday season, I will start the v2 revision which would need to include Async/await/task integration, platform services, new scheduler updates etc... Like yourself, all the projects I work on at work are bound to v1, so personally I didn't feel I was strong enough to write about v2 yet.
With WPF hat on
From my testing (that I stupidly never published or kept the results for) the biggest costs for INotifyPropetyChanged (INPC) performance is raising the event if something is bound to it. I have looked at this several times and here is my break down of the things I checked for
- Using expressions instead of strings
- Caching the INPC Eventargs instead of creating them every time a property changes
- Not assigning the INPC event to a handler before raising (as it should always be on the dispatcher, so in theory single threaded)
- Checking that the change is raised on the dispatcher and raising on the dispatcher if required
- Not performing a change check if(_myThing!=value)...before raising the event
- Using Weaving for INPC
- Raising sting.Empty (or null) as the PropertyName for bulk changes to a single VM
- Rx vs no Rx
Basically the massive thing I took away from this is that raising an INPC event when the property is bound is expensive. If you think about it, it is pretty obvious, the UI needs to do all sorts of binding magic and then actually redraw the UI. This will always cost more than a string allocation or an if check and as it turns out, even more expensive than evaluating an expression.
The other thing that can bring your app to its knees is flooding the dispatcher. If you need a highly responsive application that updates often, you definitely do not want to do the if(Dispatcher.CheckAccess()){Dispatcher.Invoke(INPC)}else{INPC}. You also want to upgrade your implementations of INPC to allow for logical batching of updates. For example if you find that updating a price on an item means the LinePrice, LinePriceIncTax, LastUpdatedDate, LineTotal & LineTotalIncTax properties all need to be updated, then you may want to consider raising INPC on the whole object (using sting.Empty or null as the propertyName). Things you need to consider are how many properties are changing in the one operation vs how many total properties do you have on the object. Again perf test to validate your assumptions.
Along the same lines, I find that often when I add to a collection, I should be doing it as a batch process. Assume I add an Item to the basket collection. It will update the Count, Item[], BasketTotal, BasketTotalIncTax & IsOverLimit properties. Now if I add 20 items to the basket, I obviously want to add all of them and then raise just once the INPC events for the above properties. The alternative is to raise the above 5 properties and the Observable Collection Changed event 20 times (120 events). This means 120 UI updates vs the potential 6.
Lastly, clear separation between who does business logic (Model) and who does concurrency (ViewModel) can save your bacon and make your code base so much easier to test and maintain. The gotcha at the moment for me is Nested collections (e.g. Orders that have OrderLines), they can prove fun to update. With Rx, I believe that the easiest way to make this work is that the ViewModel has an implementation of an ISchedulerProvider/ISchedulerService that request the Model to do concurrent work (TaskPoolScheduler) and then processes results on the DisptacherScheduler. Sprinkling ObserveOn and SubscribeOn operators in more than one layer of your code base (for the me it is the VM) can lead to poor performance and difficult to catch bugs.
I hope that helps, and doesn't sound too dogmatic.
Lee
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged.aspx
Lee Campbell http://LeeCampbell.blogspot.com
- Edited by LeeCampbell Thursday, November 08, 2012 10:33 AM
-
Monday, November 12, 2012 2:44 PM
Hi Lee,
Please don't apologise for writing a wonderful book that has helped my whole team get to grips with rx! One of rx's challenges has been the absence of decent documentation and you've done a wonderful job of remedying it!
On the wpf front, I think we've covered a lot of similar ground.
> Using expressions instead of strings
Expressions are definitely slow. I have experimented with a property wrapper that does the expression resolution as a one time operatorion. This works but has a memory overhead, which we could avoid using some static fields in the view model to cache things. The unpleasantness of manually doing these statics is how I've ended up leaning towards a T4 codegen approach with partial classes for the VMs. I was really disappointed to discover that expressions and funcs don't have any lightweight way to eval equality to support memoization etc.
- Caching the INPC Eventargs instead of creating them every time a property changes
Have done this. Not sure whether it makes much difference as these never get beyond gen 0. Did you notice any real benefit?
- Not assigning the INPC event to a handler before raising (as it should always be on the dispatcher, so in theory single threaded)
Not entirely sure what you mean here? I only raise INPC on the dispatcher so work on the basis that there are no concurrency issues.
- Checking that the change is raised on the dispatcher and raising on the dispatcher if required
Would not do this, as you end up with updates that are too granular (as you discussed later).
- Not performing a change check if(_myThing!=value)...before raising the event
Always do that on basis that expense of setting/rending is much higher than that of checking.
- Using Weaving for INPC
Not sure what weaving is, I shall have to google!
- Raising sting.Empty (or null) as the PropertyName for bulk changes to a single VM
Not tried this. Do you then have to do some funky binding?
At the collection level we have tried this by doing a collection changed event to signify a row in a grid has changed atomically rather than field by field for that row. This tested out rather slow with the grid we are currently using.
The other thing you can do with collection changed is batch up updates and disable the INotifyCollectionChanged events whilst doing the batch update and then sending a Reset event on the collection once the update is done. This works great for initial loading up of the grid. There is a point where number of updates vs number of lines in the grid already there can make this strategy degrade. The other real pain with this is that it can impact sorting/selections/grid position when you do the reset.
- Rx vs no Rx
I've tried and used rx and TPL Dataflow. There is significant overlap, although dataflow seems better suited for explicit co-ordination of threading/workers etc, whereas as thats more implied in rx which also lacks the 2 phase commit semantic in the interfaces. However Rx is much stronger on the transformation/manipulation of streams.
On the nesting front, I haven't run into any problems yet. Rather than use MVVM, you can use MVVM-P, which is similar but makes the VM a pure data contract containing just the state bound to the view, with all the logic in a presenter. The VM then has no binding to the view code behind, or presenter (and rx is very helpful here as it allows the presenter to bind to fields in the VM in a similar way that the view can bind to the VM too). This has benefits for encapsulation, testing, class complexity & resuse. Amongst other thing, a presenter also caches its syncronisation context and a group disposable to manage lifetime inside its scope.
So in a nested situation, there is nothing to stop a row in a grid having its own presenter which might subscribe to data feeds on a per row basis. This gives great encapsulation, and you aren't really crossing tiers. The outter presenter/vm tupple can hold an observable collection of child view models, which have presenters attached to them, but not directly referenced by the parent presenter. The only wrinkle is that this approach can make rows quite expensive to instantiate, so then you start getting into wanting a fast ioc container and to be able to instantiate them in the background.
-
Monday, November 12, 2012 4:07 PM
So on the WPF tangent (sorry Rx readers)....
Weaving
Weaving is the act of Rewriting the IL your C# generates. This is called Aspect Orientated Programming (AOP) and was initially popular for injecting Logging all over an application by just putting an Attribute on a method or a class. Now people also use it to inject the INPC code.
Google "INotifyPropertyChange weaving" to get started. The cost is that you have slightly slower compile times (which can be a pain if you are a Test First type person). The upside is you can use automatic properties and not have big fat properties (which personally don't bother me at all, and I use a snippet to write them so I don't see the fuss).
Atomic Row changes
You could use a collection changed event to indicate that a row has changed, but make sure you use the cheapest one. Using an event with a Reset action is really expensive, as you are effectively telling the binding engine "everything has changed, rebind all over again". By raising the INPC on the row with "" or null, you are saying the same thing but just for the row. Check the performance yourself, but my money is on the latter.
Batch collection changes
Further more, reset is not your only weapon in your arsenal. Just because the ObservableCollection<T> does not support anything else besides reset/singleAdd/singleRemove/singleMove/SingleReplace, it does not mean that you can not just extend it or implement INotifyCollectionChanged on you collections yourself. The NotifyCollectionChangedEventArgs supports adding batches with constructor overloads such as
/// <summary> /// Construct a NotifyCollectionChangedEventArgs that describes a multi-item change (or a reset). /// </summary> /// <param name="action">The action that caused the event.</param> /// <param name="changedItems">The items affected by the change.</param> /// <param name="startingIndex">The index where the change occurred.</param> public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int startingIndex) {...}If you correctly supply the starting index of the new rows and the actual new rows, you should find that your performance does not degrade (depending on if you are using good controls that respect the contract)
HTH
Lee
P.S. Just dusted off the old IntroToRx code ready to start v2 of the book (dont tell my wife)
Lee Campbell http://LeeCampbell.blogspot.com
-
Monday, November 12, 2012 8:32 PM
Hi,
FYI: I just edited my original reply. I added a new example that simply subscribes to the BehaviorSubject<T> directly instead of requiring another subject as a proxy to receive user input. I should've posted this simpler example in the first place, but at the time I was working on a project that required two separate subjects to ensure that notifications from the UI weren't mixed with updates originating from the view model. It's not a typical requirement of course.
- Dave
-
Tuesday, November 13, 2012 5:24 PM
Hi Lee,
Thanks for the extra info - turns out I do know what weaving is.
Agree re reset being expensive, especially for a single row change!
Had assumed that most grids didn't fully implement the interface for doing more specific batch updates. Perhaps its worth checking. Do you know of any vendor that conforms?
Dave - thanks for the example changes, definitely clearer now.
Dan
- Edited by Dan Harman Tuesday, November 13, 2012 5:24 PM
-
Wednesday, November 14, 2012 4:04 PM
>Do you know of any vendor that conforms?
I am pretty sure the standard WPF controls do, and we (painfully) use Infragistics and they appear to conform too (nice that they confrm to something eh).
Lee Campbell http://LeeCampbell.blogspot.com
-
Thursday, November 29, 2012 10:03 AM
"With all of the performance improvements made to Rx 2.0, I believe that creating a subscription to Subject<T> amounts to the creation of a few ephemeral objects and a couple of virtual method calls, at most, when using the First operator. "
Is there really a First operator in Rx 2.0 that pierces the monad?
The only ones that i can find are FirstAsync and FirstAsyncDefault that dont leave the monad.
-
Thursday, November 29, 2012 2:56 PM
Hi,
> Is there really a First operator in Rx 2.0 that pierces the monad?
First is marked with ObsoleteAttribute, but it's still there. If you're using VB then perhaps it's simply not listed in IntelliSense.
You can also use FirstAsync().Wait().
After thinking about this some more, I do agree that BehaviorSubject<T> should expose a property that returns the current value, to avoid the more costly and verbose alternative of FirstAsync().Wait().
I've created a new feature request in the Rx project on CodePlex.
- Dave
- Proposed As Answer by Dave Sexton Saturday, December 01, 2012 6:26 PM

