There is nothing wrong with the model, but the fact that you must subscribe to an event or register a callback means your code can end up nested and scattered to the winds. Add to that the complexity of aggregating events (for example, firing multiple asynchronous events and then waiting for them all to complete before going on) and it's enough to require a visit to the pharmacy. OK, that was a bad lead-in because of course that's where you can get your Rx (prescription).
There is a lot to explore in the framework, but you can start by visiting the main site and downloading the extensions (they are available for .NET 3.5, .NET 4.0, Silverlight 3, Silverlight 4 and JavaScript). Probably the best and easiest way to learn using Rx is to follow the hands-on labs that you can download in PDF format from this blog post.
I'd like to give a gentle introduction by demonstrating how it solved a very specific need of mine. A very common pattern is what I'll call "synchronized lists" where one list triggers a change in the other. The classic example is selecting a country to get a list of states for that country. Selecting a country means we fetch or filter a new list of states, and if we're kind to the end user we'll also give them a default (either a state or perhaps a "select one" prompt). One test would be changing the country and validating that the list of states updated correctly. Another test, important for data-binding, is to ensure that setting the country raises both the country and the state property changed events. If someone were to come along and change the setter to the private property, the property change notification would fail to fire and we'd lose our data-binding.
To test is straightforward: set up the view model, register to the event, then listen and confirm we get the properties we are looking for. Implementing it is another story. The original syntax would involve registering a lambda that adds to a list, then waiting for a particular time (to make sure all events have fired) and then check the list. There is no "clean" way to do it without timers or a counter or making assumptions about the order the properties may come in.
The Reactive Extensions simplify this tremendously. Take a look at the following code:
public void TestChangeCurrentCountry_RaisesPropertyChanged() { var properties = new List<string>(); var expected = new[] {"CurrentCountry", "CurrentState"}; // set it up _target.OnImportsSatisfied(); var propertyChanged = (from evt in Observable.FromEvent<PropertyChangedEventArgs>(_target, "PropertyChanged") select evt.EventArgs.PropertyName).Take(2); using (propertyChanged.Subscribe( properties.Add, ex => Assert.Fail(ex.Message), () => { CollectionAssert.AreEquivalent(expected, properties, "Invalid properties were raised on the notify change event."); EnqueueTestComplete(); })) { _target.CurrentCountry = _countries[1]; } }
What's going on here? I set up a list to grab properties as they are raised, and I initialize a list of expected properties. This makes the conditions of the test very clear. I'm using MEF, so what happens in unit tests is that I set up the view model with a bunch of mock objects to return my data: i.e. "if you ask for a country, I'll give you this" and so forth. I explicitly call "OnImportsSatisfied" to simulate what would happen if I were actually composing the view model with MEF (in this case, however, my test is composing it).
In this case, the view model will call the country service to populate the list, then the state service, and set defaults. All of these are fed based on the mock configurations.
Once we have the view model prepared, the Rx steps in. I set up an observable collection that is based on the property changed event. Notice we pass the expected
EventArgs
, the object to inspect and the event to listen for. The power of Rx is that it will asynchronously build the list for me as the event is raised, in this case returning the property names (because of my select statement). I am only expecting 2, so I can use the standard LINQ "Take(2)" for the list to stop after the second property changed event (otherwise, it would continue listening and building out the list ad infinitum). The using statement provides an action as items are added (note I'm using a method group to specify that the string is passed to the add method of the properties list.) If an exception is thrown, I'll fail the test gracefully with an assert. The last parameter is the action to perform when complete, which is after both properties have been added. Here, I compare the two lists and let the Silverlight Unit Testing Framework know that the test is complete.
The entire statement wraps the functionality I'll use to trigger the behaviors - in this case, changing the country to set off the property change notification.
While this is a very simple example, I encourage you to download the hands on labs to see more complex examples that compose multiple asynchronous events together in a single collection. It's a powerful framework that deserves a close look!