Как правильно подписаться на Измененную последовательность ReactiveObject?

Еще один вопрос по поводу ReactiveUi. У меня есть ViewModel для редактирования формы. Модель является ReactiveObject. Я хочу включить savecommand только тогда, когда произошли изменения объекта. Моя попытка:

var canSaveCommand =
        this.WhenAnyValue(vm => vm.CurrentClient)
            .Where(client => client != null)
            .Select(client =>
                client.Changed
            )
            .Any();

Но когда появляется форма SaveCommand уже включен. Где моя ошибка?

2 ответа

Решение

Попробуйте изменить ваш Select на SelectMany. Это тогда даст вам Наблюдаемую за изменениями, которые будут переданы в Любую вместо Наблюдаемой за Наблюдаемой за изменениями, которые будут переданы в Любую.

Вы хотите использовать Switch, а не SelectMany. SelectMany не будет отписываться от предыдущего клиента. Он объединит события всех клиентов. Переключите отписку от предыдущего клиента, прежде чем подписаться на следующего.

 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. Например, я нашел ошибку в платформе ReactiveUI, в которой вместо Switch использовался SelectMany.

тем не мение

В ReactiveUI есть встроенный способ добиться этого ясным способом

На самом деле есть другой способ добиться того, чего вы хотите, и я добавлю его в другой ответ, просто чтобы показать вам, как использовать ReactiveUI.

var canSaveCommand =
        this
          .WhenAnyObservable(vm => vm.CurrentClient.Changed)
          .StartWith(false);

Обратите внимание, что null не должен обрабатываться подробно, хотя вы должны начать с false, чтобы убедиться, что значение существует, когда нет доступных для начала наблюдений.

WhenAnyObservable

WhenAnyObservable во многом похож на оператор Rx CombineLatest, поскольку он отслеживает одну или несколько наблюдаемых и позволяет определять проекцию на основе последнего значения каждого из них. WhenAnyObservable отличается от CombineLatest тем, что его параметры являются выражениями, а не прямыми ссылками на целевые наблюдаемые. Влияние этой разницы заключается в том, что часы, созданные с помощью WhenAnyObservable, не привязаны к конкретным наблюдаемым экземплярам, ​​присутствующим во время подписки. То есть наблюдаемая, на которую указывает выражение, может быть заменена позже, а результаты новой наблюдаемой все равно будут зафиксированы. Примером того, где это может пригодиться, является случай, когда представление хочет наблюдать наблюдаемую на модели представления, но модель представления может быть заменена в течение времени существования представления. Вместо необходимости повторной подписки на целевую наблюдаемую информацию после каждого изменения модели представления, вы можете использовать WhenAnyObservable, чтобы указать "путь" для просмотра. Это позволяет вам использовать одну подписку в представлении, независимо от срока жизни целевой модели представления.

Другие вопросы по тегам