In part 1 and 2 of the series Reactive Extensions in theory and practice we covered some of the theory of Rx and introduced a nice visualization method with marble diagrams.
In this post we will have a closer look at the operators Select
, Zip
and Skip
and review a sample application where those operators are extremely useful.
The application is a very simple WPF application where you can draw lines in a window. For each line the distance will be displayed. In fact the code for this program is so simple and declarative that it really shows the shining beauty and elegancy of Rx.
The source code of the app can be downloaded here.
Operators
Let’s first introduce the operators that are involved in this example.
Select
Select
is a transforming operator that projects the elements of an observable sequence into a new form by applying the function f
to each element.
Skip
Skip
is a filtering operator that bypasses the first n
elements of a sequence. The result is a sequence that emits all elements of the source sequence except the first n
elements.
Zip
Zip
is a combining operator that merges two sequences by applying a function f
to each pair of elements. Pairs are created by combining each element of the first sequence with the element of the second sequence that has the same index as the first element.
Operators in practice
To see the operators in action we will review this sample application that lets us draw lines on the UI. For each line the length is displayed. The length is the distance between two positions on the UI represented by two Point
instances. In this case the coordinates of the points are determined relatively to the canvas control of the main window.
Let’s go through the steps that are necessary to implement this.
First let’s take a look at the simple class Line
which is the main type used in the application:
public class Line
{
public Point PointA { get; private set; }
public Point PointB { get; private set; }
public double Length
{
get
{
var d = Point.Subtract(PointA, PointB);
return Math.Sqrt(Math.Abs(d.X * d.X - d.Y * d.Y));
}
}
private Line(Point a, Point b) { PointA = a; PointB = b; }
public static Line Create(Point a, Point b)
{
return new Line(a, b);
}
}
The implementation is straightforward. The factory method Create
takes two points and returns a new Line
instance. It is just for convenience because it simplifies the syntax when used instead of the constructor with Rx operators. Length
calculates and returns the distance between the two points. For the sake of simplicity the value of Length
is not stored locally which would be an optimization.
Now we have to convert the mouse button click events into an observable stream. This can be done with the Observable.FromEventPattern<TEventArgs>
function. As the first parameter we have to provide the object instance that exposes the mouse click event which is the MainWindow
instance itself. So we can pass in this
here. The second parameter is the name of the event that we are interested in which is "MouseUp"
. The result is an observable sequence of EventPattern<MouseButtonEventArgs>
:
Observable.FromEventPattern<MouseButtonEventArgs>(this, "MouseUp")
Next we have to convert each EventPattern
into a Position of type Point
. We could do this by just using a lambda expression. But instead we define the little nested helper function getPosition
. The benefit of the helper function is that it makes the code more compact later when the function is passed to the Select
operator. The position is determined relatively to the Canvas
UI control named _box
.
Func<EventPattern<MouseButtonEventArgs>, Point> getPosition =
args => args.EventArgs.GetPosition(_box);
Finally we need a function that takes a line and updates the UI by drawing the line and displaying its length. Therefore we define a private method UpdateUi
. The details here are WPF specific and not really relevant to us right now.
Composing the operators
Now we have defined all necessary functions. The only thing left to do is to transform and combine the observable sequence of click events by composing and applying the Rx operators Select
, Skip
and Zip
.
The idea is to create lines by taking each point of the sequence and combine it with its predecessor.
The solution is simple and elegant. After we transformed the click stream to positions with Select(getPosition)
we shift the sequence by one with Skip(1)
. Then we merge the shifted sequence with the original sequence with Zip
and pass in the factory method to create lines. That’s it!
There is no need to keep the state e.g. the last clicked position in a local or instance variable.
Here is marble diagram that visualizes how this works:
Connecting the observer
Finally we have to connect the observer (which is the UI in this case) with the observable by calling the extension method Subscribe
and pass in the UpdateUi
function. Here is the code that is responsible for composing all the functions to create the program:
var points = Observable
.FromEventPattern<MouseButtonEventArgs>(this, "MouseUp")
.Select(getPosition);
points
.Zip(points.Skip(1), Line.Create)
.Subscribe(UpdateUi);
Summary
We have seen how a program can be composed by using observable sequences and applying Rx operators to them. The source code of this program is very concise and declarative. Also there is no state involved (except for the UI off course) that we have to be cautious of.
Using Reactive Extensions in event-based applications leads to code that is simple and elegant and therefore easy to understand, maintain and extend.