It appears to have gone mostly under the radar, but the Reactive Framework is now out in the wild. The latest release of the Silverlight Toolkit includes System.Reactive.dll, mostly to facilitate the testing of the controls found within the toolkit. Jafar Husain broke the news earlier this week with this excellent post.
Today I want to do a demo that is very similar to the previous demos I’ve done, but of course this time I’ll be using the real Reactive Framework rather than my crappy attempt at implementing it that I’ve inflicted upon you in earlier posts. So far my experiments with using System.Reactive.dll with windows forms have not compiled properly so I’ll switch to using Silverlight for my examples. For now I will continue to focus on exposing button click events as an IObservable. Here is the code:
IObservable<Event<RoutedEventArgs>> clicks = Observable.FromEvent<RoutedEventArgs>(button1, "Click"); int count = 0; clicks.Subscribe(() => count++); IObservable<string> messages = from c in clicks select string.Format("Clicked {0} time{1}", count, count > 1 ? "s" : ""); messages.Subscribe(s => button1.Content = s);
And here is the application:
If you are in a RSS reader, you probably won’t be able to see the embedded Silverlight app above. Pop this post out into its own browser tab so that you can revel in the glory of a button that tells you how many times its been clicked!
Lets take a look at what this code is doing. The first step is to convert from an Event to an IObservable:
IObservable<Event<RoutedEventArgs>> clicks = Observable.FromEvent<RoutedEventArgs>(button1, "Click");
The Observable class defines a large swath of extension methods for creating and manipulating IObservables. This overload of the FromEvent method is simple to use, but unfortunately it makes use of a magic string (“click”). There is another overload for FromEvent that allows us to do the same thing with compile-time safety, but it is more verbose. I’ve wrapped it in an extension method:
public static IObservable<Event<RoutedEventArgs>> GetClicks(this Button button) { return Observable.FromEvent((EventHandler<RoutedEventArgs> genericHandler) => new RoutedEventHandler(genericHandler), routedHandler => button.Click += routedHandler, routedHandler => button.Click -= routedHandler); }
This overload takes three functions as arguments. One to convert from a generic event handler (EventHandler<T>) to the specific event handler that the Click event uses (RoutedEventHandler). The conversion is straightforward, because the two event handlers have the same signature. The other two functions add and remove the handler. This extension method can be used like so:
IObservable<Event<RoutedEventArgs>> clicks = button1.GetClicks();
It would not surprise me if we eventually see extra libraries that define many of these extension methods for us. Ideally they would be unnecessary, and we could simply write:
IObservable<Event<RoutedEventArgs>> clicks = button1.GetObservableEvent(b => b.Click);
But unfortunately the dreaded “The event 'System.Windows.Controls.Primitives.ButtonBase.Click' can only appear on the left hand side of += or –=” message rears its ugly head. Perhaps in C# 5 the story will be different.
Moving on, the code initializes a count variable and subscribes an increment function to the clicks:
int count = 0;
clicks.Subscribe(() => count++);
You’ll notice that I am simply passing an Action to the Subscribe method, rather than an IObserver. The Reactive Framework won’t force you to use an IObserver if all you want to do is call a function when a new event occurs.
Finally, the code converts the stream of click events into a stream of messages and subscribes to it:
IObservable<string> messages = from c in clicks select string.Format("Clicked {0} time{1}", count, count > 1 ? "s" : ""); messages.Subscribe(s => button1.Content = s);
There are lots of other ways in which to rewrite today’s example code. This version is very short and still quite readable:
int count = 0; button1.GetClicks().Select(x => ++count) .Subscribe(() => button1.Content = string.Format("Clicked {0} time{1}", count, count > 1 ? "s" : ""));
Or I could implement IObserver:
public class CountingButtonObserver : IObserver<Event<RoutedEventArgs>> { private int _count = 0; public Button Button { get; set; } public void OnNext(Event<RoutedEventArgs> value) { _count++; Button.Content = string.Format("Clicked {0} time{1}", _count, _count > 1 ? "s" : ""); } public void OnError(Exception exception) {} public void OnCompleted() {} }
And use it like so:
button1.GetClicks().Subscribe(new CountingButtonObserver{ Button = button1});
Well, that’s probably enough for today. I’m going to continue to explore the Reactive Framework and see what interesting things I can find. Oh, and let me know if you come across any documentation for it – so far I haven’t found any and it can be quite a struggle to make sense of the various extension methods that are available.
I’ll be pushing my experiments with the Reactive Framework to a github repo.