如何正确订阅 ReactiveObject 的更改序列?

Але*_*вой 2 linq reactive-programming system.reactive reactiveui

关于 ReactiveUi 的另一个问题。我有一个用于编辑表单的 ViewModel。模型是 ReactiveObject。我只想在对象发生更改时启用 savecommand。我的尝试:

var canSaveCommand =
        this.WhenAnyValue(vm => vm.CurrentClient)
            .Where(client => client != null)
            .Select(client =>
                client.Changed
            )
            .Any();
Run Code Online (Sandbox Code Playgroud)

但是当表单出现时SaveCommand已经启用了。我的错在哪里?

bra*_*ing 5

您想使用Switch而不是 SelectMany。SelectMany 不会取消订阅以前的客户端。它将合并来自所有客户端的事件。在订阅下一个之前切换取消订阅前一个客户端。

 var canSaveCommand =
            this.WhenAnyValue(vm => vm.CurrentClient)
                .Where(client => client != null)
                .Select(client =>
                    client.Changed
                )
                .Switch()
                .Any();
Run Code Online (Sandbox Code Playgroud)

例如下面的代码就清楚了。假设我们有一个名为AudioChannelIt的类,它生成我们可以处理并发送到扬声器的音频帧。

 public class IAudioChannel {
     public IObservable<AudioFrame> AudioFrameObservable {get;} 
 }
Run Code Online (Sandbox Code Playgroud)

然后我们可能有一个用户可以选择的音频节点列表,但我们只想要最新的发送音频到扬声器。下面的类使当前选择的音频节点作为可观察对象可用。

public class AudioListViewModel {
    public class IObservable<IAudioChannel> CurrentAudioChannelObservable {get;}

}
Run Code Online (Sandbox Code Playgroud)

现在考虑以下代码

AudioListViewModel viewModel;

viewModel
    .CurrentAudioChannelObservable
    .SelectMany(current=>current.AudioFrameObservable)
    .Subscribe(frame=>frame.Play());
Run Code Online (Sandbox Code Playgroud)

对比

AudioListViewModel viewModel;

viewModel
    .CurrentAudioChannelObservable
    .Select(current=>current.AudioFrameObservable)
    .Switch()
    .Subscribe(frame=>frame.Play());
Run Code Online (Sandbox Code Playgroud)

在第一个版本中,随着我们更改音频节点的选择,我们添加了越来越多的订阅。音频输出很快就变成一堆乱七八糟的混合通道。在第二个版本中,一次只订阅一个频道,音频输出仅播放来自单个频道的输出。

许多人在开始使用 RX 时都会犯这个错误。例如,我在 ReactiveUI 框架中发现了一个使用 SelectMany 而不是 Switch 的错误。

然而

在 ReactiveUI 中有一个内置的方式来以清晰的方式实现这一点

实际上还有另一种方法可以实现你想要的,我会把它放在另一个答案中,只是为了向你展示如何使用 ReactiveUI。

var canSaveCommand =
        this
          .WhenAnyObservable(vm => vm.CurrentClient.Changed)
          .StartWith(false);
Run Code Online (Sandbox Code Playgroud)

请注意,虽然您应该以false开头以确保在没有可观察的值开始时存在值,但不必显式处理 null 。

WhenAnyObservable

WhenAnyObservable 的行为很像 Rx 运算符 CombineLatest,因为它监视一个或多个 observable,并允许您根据每个的最新值定义投影。WhenAnyObservable 与CombineLatest 的不同之处在于它的参数是表达式,而不是对目标observable 的直接引用。这种差异的影响是 WhenAnyObservable 设置的 watch 与订阅时存在的特定 observable 实例无关。也就是说,表达式所指向的 observable 可以在后面替换,并且新的 observable 的结果仍然会被捕获。这可以派上用场的一个例子是当视图想要观察视图模型上的 observable,但视图模型可以在视图的生命周期内被替换。无需在每次更改视图模型后重新订阅目标可观察对象,您可以使用 WhenAnyObservable 指定要观看的“路径”。这允许您在视图中使用单个订阅,而不管目标视图模型的生命周期如何。