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#. So far we have looked at representing sequences of items with
IEnumerable<T>, its expression-tree-based cousin
IQueryable<T>, and processing such sequences with LINQ. Now we'll look at an alternative representation of the same abstraction.
A basic assumption made by both
IQueryable<T> is that the information source (whether it's a data structure, an algorithm, or some sort of database) is able to provide information on demand. Code using these types asks for results either using
foreach, or a LINQ operator that evaluates an entire query such as
However, there is another way to model sequences of things: the source might produce items when it is good and ready to. This is sometimes referred to as 'push' and it's the model Rx uses. It models this with an interface called
The basic idea underpinning
IObservable<T> is identical to that of
IEnumerable<T>: both represent a sequence of items—one damn thing after another. The only difference is that the source gets to decide when the next item is produced.
IObservable<T> interface is a sort of mirror image of
IEnumerable<T>. Just as the latter has a corresponding
IEnumerator<T> interface used when iterating through the items,
IObservable<T> has a corresponding
IObserver<T>. But whereas with
IEnumerable<T> you ask for an
IEnumerator<T> when you're ready to start working through the sequence, with
IObservable<T>, you must supply an
IObserver<T> when you're ready for items, because the
IObservable<T> is going to invoke methods on you for each item.
IObserver<T> has three methods:
OnNext(T), which the source invokes for each item it produces,
OnCompleted, which it invokes to tell you the sequence has finished (the logical equivalent of
OnError, which it invokes to report an error (the logical equivalent of the
MoveNext() method throwing an exception).
So enumerables and observables are logically equivalent (to the extent that the Rx libraries provide adapters that can convert from one to the other). It is just that sometimes, one will be a more natural way to represent some sequence than the other.
IObservable<T> is the more natural way to represent events.
Rx includes a complete LINQ provider. More than complete in fact: in addition to all the operators available in other LINQ providers, Rx adds several new ones. (The team also released Ix, the "Interactive Extensions for .NET" which essentially provides versions of all these additional operators for
IQueryable<T>.) So given some source of events, you can filter them with a
Where clause, or do any of the other things LINQ supports.
Rx's 'push' world offers a particular distinction that we have also seen in the 'pull' world with
IQueryable<T>: in addition to
IObservable<T>, Rx also defines
IQbservable<T> (note the 'Q'). This is essentially a version of
IObservable<T> but one where all the LINQ operators take lambdas in the expression tree form (whereas for a regular
IObservable<T> the standard operators all take ordinary delegates—references to methods).
In the fourth part of this series, we'll take a look at Reaqtive.