Mason Wheeler
Mason Wheeler

Reputation: 84540

Observable.Do never fires, even when the underlying IObservable changes

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 IEnumerables,) 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

Answers (2)

Erwin Kuhn
Erwin Kuhn

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

Shlomo
Shlomo

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

Related Questions