Observable.Do 从不触发,即使底层 IObservable 发生变化

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

我在 ReactiveUI 和 System.Reactive.Linq 中学习了一些速成课程,因为我发现我需要的 UI 库将它用于所有事情。大部分看起来都很好理解,但是有一个操作没有做任何事情。

我有一个控件,我需要在两个地方使用它的值。我有一个 IObservable<T> 代表它的值,我使用如下:

情况 1:我需要将一个值与另一个可观察值相结合,从而将一个值提供给另一个可观察值。所以我用 Observable.CombineLatest(myObservable, otherObservable, (m, o) => ProduceValue(m, o)) 这更新完全符合预期。由此,我知道 myObservable 正在正确触发更新。

案例2: 我需要在其他地方,在不可观察的上下文中使用这个值。所以:myObservable.Do(v => UpdateViewModelWith(v))这永远不会触发。 我已经通过在 lambda 中放置一个断点并在调试器下 运行 对其进行验证。

从案例 1 我知道 observable 正在正确触发。据我了解,可观察对象在概念上很像事件,(有一堆机制让它们感觉更像 IEnumerables,)并且像事件完全能够接受多个听众,所以事实上有两个他们应该不是问题。 (通过更改两个侦听器的设置顺序进行验证,这不会对观察到的行为产生任何变化。)那么是什么导致案例 2 永远不会 运行?

我猜你正在使用一个没有任何订阅者的可观察对象,这意味着它永远不会触发并且 Do 永远不会被激活。

你想要的功能很可能是IObservable<T>.Subscribe<T>(Action<T> action):

myObservable.Subscribe(v => UpdateViewModelWith(v))

DoSubscribe的区别在于Do是副作用。这是在管道一侧发生的事情:“给我一些值,哦,顺便说一下,在旁边做这个。” 但是如果没有管道,这些值永远不会发送。

另一方面Subscribe注册一个观察者。它实际上创建了一个管道,通过告诉 observable:“嘿有人在看,发送东西!”

为了更好地理解它的含义,您还可以查看 return 类型:

  • Do return 一个 IObservable<T>,因为未使用可观察对象。
  • Subscribe return 是一个 IDisposable,因为它注册了一个观察者。

重要提示:一定要包含using System,否则你只会看到IObservable<T>.Subscribe<T>(IObserver<T> observer).

顺便说一句,我今天早上也对 Do 和 Subscribe 感到困惑,并在非常好的 Reactive Slack 上得到了帮助。如果您正在使用 Rx 或 ReactiveUI,我强烈建议您加入!

@Erwin 的回答很接近。只是为了详细说明:

Do,像大多数 Rx 相关函数一样,是一个 运算符 。没有订阅,运营商什么都不做。例如:

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. 

RangeSelectDo 都是运算符,它们在没有订阅的情况下什么都不做。如果你想做任何事情,你需要订阅。

var subscription = logged.Subscribe(i => Console.Writeline($"Subscribe: {i}");

输出:

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

通常,副作用代码(非功能代码)应该驻留在 Subscribe 函数中。 Do 最好用于 logging/debugging。因此,如果我想记录原始的非平方整数,我可以执行以下操作。

var chainedSub = Observable.Range(0, 5)
    .Do(i => Console.WriteLine($"Original int: {i}"));
    .Select(i => i * i)
    .Subscribe(i => Console.Writeline($"Subscribe: {i}");

在纯 Rx.NET 中(没有 ReactiveUI),只有一种方法可以获得订阅:各种 Subscribe 重载。然而,ReactiveUI 确实有一堆函数可以自己创建订阅,所以你不必处理它们(比如 ToProperty)。如果您使用的是 ReactiveUI,那么这些可能是比 Subscribe.

更好的选择