您想使用 Switch 而不是 SelectMany。 SelectMany 不会取消订阅前一个客户端。它将合并来自所有客户端的事件。 Switch 在订阅下一个客户端之前取消订阅前一个客户端。
var canSaveCommand =
this.WhenAnyValue(vm => vm.CurrentClient)
.Where(client => client != null)
.Select(client =>
client.Changed
)
.Switch()
.Any();
例如下面的代码就很清楚了。假设我们有一个名为AudioChannel 的类,它生成我们可以处理的音频帧并将其发送到扬声器。
public class IAudioChannel {
public IObservable<AudioFrame> AudioFrameObservable {get;}
}
然后我们可能有一个用户可以选择的音频节点列表,但我们只希望向扬声器发送最新的音频。下面的类将当前选择的音频节点作为可观察对象提供。
public class AudioListViewModel {
public class IObservable<IAudioChannel> CurrentAudioChannelObservable {get;}
}
现在考虑下面的代码
AudioListViewModel viewModel;
viewModel
.CurrentAudioChannelObservable
.SelectMany(current=>current.AudioFrameObservable)
.Subscribe(frame=>frame.Play());
对比
AudioListViewModel viewModel;
viewModel
.CurrentAudioChannelObservable
.Select(current=>current.AudioFrameObservable)
.Switch()
.Subscribe(frame=>frame.Play());
在第一个版本中,随着我们更改音频节点的选择,我们添加了越来越多的订阅。音频输出很快就变成了混杂的混音通道。在第二个版本中,一次只订阅一个频道,音频输出只播放单个频道的输出。
很多人在开始使用 RX 时都会犯这个错误。例如,我发现一个bug in the ReactiveUI framework 使用 SelectMany 而不是 Switch。
然而
在 ReactiveUI 中有一个内置的方法可以清晰地实现这一点
实际上还有另一种方法可以实现您想要的,我将它放在另一个答案中只是为了向您展示如何使用 ReactiveUI。
var canSaveCommand =
this
.WhenAnyObservable(vm => vm.CurrentClient.Changed)
.StartWith(false);
请注意,尽管您应该以 false 开头以确保在没有可观察对象可开始时存在值,但不必明确处理 null。
WhenAnyObservable
WhenAnyObservable 的行为很像 Rx 运算符 CombineLatest,在
它监视一个或多个 observables 并允许您定义一个
基于每个的最新值的投影。 WhenAnyObservable
与 CombineLatest 的不同之处在于它的参数是表达式,
而不是直接引用目标 observables。的影响
这个区别是WhenAnyObservable设置的手表不是
与当时存在的特定可观察实例相关联
订阅。也就是说,表达式所指向的 observable 可以
稍后替换,新的 observable 的结果仍然是
捕获。这可以派上用场的一个例子是当一个视图
想要观察视图模型上的可观察对象,但视图模型可以
在视图的生命周期内被替换。而不是需要
每次更改视图模型后重新订阅目标可观察对象,
您可以使用 WhenAnyObservable 指定要观看的“路径”。这
允许您在视图中使用单个订阅,而不管
目标视图模型的生命周期。