Empowering ObservableCollection with Rx
The ability to register for changes to a collection isn’t something that all languages allow. Indeed, it’s not something we might require for every project but it’s arguably a very useful pattern. Often collections are used as data sources, backing a user interface such that the UI must refresh when the data source changes. When collections are not observable, we can always have callers manually update the UI but a degree of automation is lost and when we litter the responsibility for UI updates around, it becomes error-prone. With observable collections, the underlying data source can be ‘bound’ to specific UI elements. It can be altered from anywhere, allowing the owner of the data source to automatically update any associated UI with no additional refresh calls – a core structural component of the increasingly popular MVVM software design pattern.
ObservableCollection is a C# collection that offers this kind of functionality.
For those readers who may be unfamiliar with this versatile collection class, you can think of it much like a mutable array or list. Usually written as
ObservableCollection<T>, it conforms to the standard protocols expected of a collection, such as
IEnumerable<T>. So common iterative and reductive operations can be performed upon it as usual. However, it has an additional, somewhat magical property: interested observers can be notified of changes to the items stored within, through the Event Handler pattern.
Observing Change - The Event Handler Pattern
The Event Handler pattern in C#, with its powerful and familiar
-= syntax for adding and removing subscribers, is strict and safe way to manage a list of subscribers. Those interested in receiving events can ‘add’ a handler and later ‘remove’ it when they no longer require updates. Below is a basic example of delegate subscription with an ObservableCollection for integers.
Here, we create a new ObservableCollection and immediately delegate the local method
OnCollectionChanged to receive updates. As a delegate it’s signature must match the expected signature for this event, which typically includes a sender and a set of arguments. When we consider that, for each operation on the collection,
NotifyCollectionChangedEventArgs provides not only an action of type
NotifyCollectionChangedAction from which we can determine what kind of operation occured, but also all the objects affected, we begin to see the power and flexibility at our disposal here.
Event Handling In Practice
Putting aside the ability to query what changed in the array for a moment, consider a very basic case of a view that should change colour when a user has selected some items from a list, but less than ten items in total. Perhaps this could be used to provide some visual feedback to the user about the validity of the data source contents. Of course we can use the event handler and do some checks in our callback so let’s first examine what that might look like by extending our earlier example.
N.B. With C# now being used across many platforms, each employing their own unique UI frameworks, all user interface components here are intentionally purely notational.
Separating Concerns using Rx
That’s quite neat but of course we’re omitting all the boilerplate here. We’re also ignoring the fact that typically in many software design patterns, like MVVM, the button will exist on a view, distinctly separated from the data source. So, how do we get the information out without exposing the data source’s collection or requiring that the data source take a reference to the button? One simple way is to expose the event as an observable sequence on the data source as shown below.
We’re not in Kansas Any More
This may seem a somewhat disarming – the familiar simplicity of the event handler pattern is seemingly cast aside for this more complex, voluminous alternative. So what gives? As we will see, there are some significant benefits for a small increase in localised complexity.
Let’s start by dissecting it a little.
Firstly, notice that we’ve crossed over to the world of Reactive Extensions (Rx), which deals with asynchronous observable sequences. The
Observable.FromEvent<TDelegate, TEventArgs> operator found in
System.Reactive.Linq can transform the event into an observable sequence using some simple rules:
handler => (sender, e) => handler(e)converts given the
NotifyCollectionChangedEventHandlerto a delegate, which is used in the following two steps.
handler => myData.CollectionChanged += handleris responsible for attaching the given event handler to the underlying delegate.
handler => myData.CollectionChanged -= handlerremoves it as subscribers are disposed.
Once the transformation is complete, we then further transform the result into an immutable array that can be publicly observed using Rx’s LINQ-style operator
Select. Notice that the ObservableCollection is now private and so observers to
DataChanged get a completely immutable version of the data, allowing the data source complete internal control over mutations. Other than that, the output hasn’t really changed significantly: the subscription model has changed from an event to an observable but that’s about it. However, now that we have an IObservable instance, we can easily operate on it internally and expose convenient and accurate observable streams about expected, internally-defined conditions.
On a basic level, we can now transform the observable stream of data changes to a stream of
Color values for our ‘sucess/fail’ conditions again using the
Select operator. It should be clear to see then how a view model subscribing to this property, could automatically toggle our externally managed view’s colour as the collection changes and the conditions for the user to continue are met or violated. We can even specify the initial conditions using the
StartWith operator – a state which may otherwise be left to pure assumption for subscribers.
Furthermore, we can easily define new conditions, change existing conditions or even allow external sources to define their own conditions in-situ if they so desire. Of course this is all still possible with the event pattern, but part of the beauty of this approach is how it simplifies alterations to conditions. It provides quick, easy access to brand new publicly observable sequences on the data source that meet specific criteria. For example, a stream that emits
true when items are added and
false when items are removed. It’s not immediately clear why you might need such a sequence, but the fact remains that it’s trivial once the basic stream is established.
Savvy C# developers might notice of course that this is achievable through the use of
INotifyPropertyChanged or possibly with popular MVVM frameworks such as ReactiveUI or WPF. However, we may not always be in a position to include our favourite frameworks and it could be argued that
INotifyPropertyChanged can introduce wieldy boilerplate, so it is prudent to examine this approach also. This is where the event handler pattern meets Rx in way that can tangibly improve the consistency, repeatability and stability of observable data sources, however we choose to use them.