I'm getting a bit of a crash course in ReactiveUI and System.Reactive.Linq
after finding that a UI library I need uses it for everything. It seems to be pretty understandable for the most part, but there's one operation that isn't doing anything.
I have a control whose value I need to use in two places. I have an IObservable<T>
representing its value, which I'm using as follows:
case 1: I need to feed a value to another observable, by combining this with another observable value. So I produce it using
Observable.CombineLatest(myObservable, otherObservable, (m, o) => ProduceValue(m, o))
This updates exactly as expected. From this, I know that myObservable
is firing updates correctly.
case 2:
I need to use this value elsewhere, in a non-observable context. So: myObservable.Do(v => UpdateViewModelWith(v))
. This never fires. I've verified that by putting a breakpoint in the lambda and running it under the debugger.
From case 1 I know that the observable is firing correctly. As I understand it, observables are conceptually a lot like Events, (with a bunch of machinery to make them feel more like IEnumerable
s,) and like Events are perfectly capable of accepting multiple listeners, so the fact that there's two of them shouldn't be a problem. (Verified by changing the order in which the two listeners are set up, which produces no change in observed behavior.) So what can cause case 2 to never run?
@Erwin's answer is close. Just to elaborate:
Do
, like most Rx-related functions, is an operator. Operators do nothing without subscriptions. For example:
var source = Observable.Range(0, 5);
var squares = source.Select(i => i * i);
var logged = squares.Do(i => Console.WriteLine($"Logged Do: {i}));
var sameThingChained = Observable.Range(0, 5)
.Select(i => i * i)
.Do(i => Console.WriteLine($"Chained Do: {i}));
//until here, we're in no-op land.
Range
, Select
and Do
are all operators, which do nothing without a subscription. If you want any of this to do anything, you need a subscription.
var subscription = logged.Subscribe(i => Console.Writeline($"Subscribe: {i}");
Output:
Logged Do: 0
Subscribe: 0
Logged Do: 1
Subscribe: 1
Logged Do: 4
Subscribe: 4
Logged Do: 9
Subscribe: 9
Logged Do: 16
Subscribe: 16
Generally, side-effect code (non-functional code) should reside in a Subscribe
function. Do
is best used for logging/debugging. So if I wanted to log the original, non-square integer, I could do as follows.
var chainedSub = Observable.Range(0, 5)
.Do(i => Console.WriteLine($"Original int: {i}"));
.Select(i => i * i)
.Subscribe(i => Console.Writeline($"Subscribe: {i}");
In pure Rx.NET (without ReactiveUI), there's only one way to get a subscription: the various Subscribe
overloads. However, ReactiveUI does have a bunch of functions that create subscriptions themselves so you don't have to deal with them (like ToProperty
). If you're using ReactiveUI, those are probably a better choice than Subscribe
.