Reputation: 84540
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?
Upvotes: 1
Views: 678
Reputation: 41
I would guess that you are using an observable without any subscribers, which means it never fires and Do
is never activated.
The function that you want is likely IObservable<T>.Subscribe<T>(Action<T> action)
:
myObservable.Subscribe(v => UpdateViewModelWith(v))
The difference between Do
and Subscribe
is that Do
is a side effect. It's something that happens on the side of the pipeline: "pass me some values, oh and, by the way, do this on the side." But if there is no pipeline, the values are never sent.
On the other hand Subscribe
registers an observer. It actually creates a pipeline, by telling the observable: "hey someone is watching, send stuff!"
To better understand what it means, you can also look at the return types:
Do
returns an IObservable<T>
, since the observable is not consumed.Subscribe
returns an IDisposable
, since it registers an observer.Important: make sure to include using System
, otherwise you will only see IObservable<T>.Subscribe<T>(IObserver<T> observer)
.
By the way, I also got confused regarding Do and Subscribe just this morning and received help on the very nice Reactive Slack. I highly recommend joining if you're working with Rx or ReactiveUI!
Upvotes: 4
Reputation: 14350
@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
.
Upvotes: 5