To understand Reaqtor, it is necessary to understand Rx (the Reactive Extensions for .NET). And to understand Rx, it is necessary to understand how C# works with sequences of items. In this series I will outline the ideas at the heart of Reaqtor, and how they are handled in C#. Last time, we saw how Rx defines a push-oriented alternative representation of the basic sequence abstraction more often represented by
IEnumerable<T>. Now we're going to see how Rx has evolved into Reaqtive, adding some new capabilities that open the door for Rx as a service.
Rx has been around for some time. Early versions were released directly by Microsoft. It was open sourced in 2012, and then moved under the .NET Foundation's control a couple of years later. After a few slightly moribund years, community activity started to increase in 2017, and it continues to be actively maintained. However, Rx also lived on in parallel inside Microsoft in a form that was not public for many years. Part of what Microsoft has made available in the Reaqtor libraries is in effect the new version of Rx, now in various namespaces under Nuqleon, Reaqtive & Reaqtor.
We've already seen a couple of 'dimensions' with
IEnumerable<T> and Rx. We can think of the expression tree/callback choice (to
Q or not to
Q) as one particular dimension, and you can choose to stand in either the callback side or the expression tree side. This is independent of the pull/push choice, which is another dimension. We can choose between two options in that dimension too, leading to four possibilities:
IEnumerable<T> (pull, callbacks),
IQueryable<T> (pull, expressions),
IObservable<T> (push, callbacks) or
IQbservable<T> (push, expressions).
Reaqtive adds various extra dimensions. One is the nature of subscriptions. With classic Rx, when you subscribe to an
IQbservable<T>) it returns an
IDisposable, and you can unsubscribe (i.e., decide you no longer want to receive events—the logical equivalent of executing a
break statement in a
foreach over an
IEnumerable<T>) by calling
Dispose. But Reaqtive elevates subscriptions to a more sophisticated level, making them entities in their own right, with some capabilities for walking over the participants of a subscription (e.g., enabling all the operators in a subscription to share a context). This is reflected in the
IQubscribable<T> interfaces. (It does not extend the pull world in this way, so there's no
IReactiveEnumerable<T>, for example. Not every conceivable position across all the dimensions is populated.) Another dimension concerns whether entities such as observable sources or subscriptions are identified purely by .NET object identity, or whether they have associated URIs acting as persistent names. In the latter case we have various interfaces with
Reactive in their names such as
IReactiveQbservable<T>. (Again, not every possible combination exists; these
Reactive forms all have the same strong subscription model as
ISubscribable<T>, and there's no
Reactive flavour without that feature.)
Yet another dimension is async. Reaqtive defines
IAsyncReactiveQbservable<T>. (Once more, not every conceivable corner of this hyperspace is populated: there is no
IAsyncObservable<T> for example. So if you want async, you are also required to be on the 'first class subscriptions' side of the 'reaqtive' dimension.) These are useful in distributed scenarios, where establishing a subscription might entail communicating with an external service, an inherently asynchronous operation.
Each of these dimensions has a role to play in how Reaqtor builds on the capabilities of Rx, as we'll see in the final two parts.
In the fifth part of this series, we'll examine remotable expressions.