Yesterday I watched the screencast Writing your first Rx Application (on Channel 9) where Wes Dyer shows how to implement Drag 'n' Drop using Reactive Extensions (Rx). Something that I still don't understand:
Towards the end of the screencast, Wes Dyer types in the following:
var q = from start in mouseDown
from delta in mouseMove.StartsWith(start).Until(mouseUp)
.Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>
new { X = cur.X - prev.X, Y = cur.Y - prev.Y }))
select delta;
Briefly, q
is an observable that pushes the mouse move coordinate deltas to its subscribers.
What I don't understand is how the mm.Zip(mm.Skip(1), ...)
can possibly work!?
As far as I know, IObservable
is not enumerable in the sense that IEnumerable
is. Thanks to the "pull" nature of IEnumerable
, it can be iterated over again and again, always yielding the same items. (At least this should be the case for all well-behaved enumerables.) IObservable
works differently. Items are pushed to the subscribers once, and that was it. In the above example, mouse moves are single incidents which cannot be repeated without having been recorded in-memory.
So, how can the combination of .Zip
with .Skip(1)
possibly work, since the mouse events they're working on are single, non-repeatable incidents? Doesn't this operation require that mm
is "looked at" independently twice?
For reference, here's the method signature of Observable.Zip
:
public static IObservable<TResult> Zip <TLeft, TRight, TResult>
(
this IObservable<TLeft> leftSource, // = mm
IObservable<TRight> rightSource, // = mm.Skip(1)
Func<TLeft, TRight, TResult> selector
)
P.S.: I just saw that there's another screencast on the Zip
operator which is quite insightful.