WedX - журнал о программировании и компьютерных науках

Объединение и фильтрация нескольких событий с помощью WhenAnyObservable

У меня есть ViewModel с:

  • список ReactiveList‹ MyObject>
  • один MyObject
  • логическое значение IsBusy

Я хотел бы отключить команду, когда ни одно из свойств «Активно» из списка MyObject или из одного MyObject не является истинным или в любом случае, когда IsBusy имеет значение true.

Прежде чем добавить IsBusy к картинке, лучшим решением, которое я придумал, было:

var canSave = this.WhenAnyObservable(
    x => x.MyObjectsList.ItemChanged, x => x.MyObject.Changed)
    .Where(x => x.PropertyName == "Active")
    .Select(_ => MyObjectsList.Any(x => x.Active) || MyObject.Active);

SaveCommand = ReactiveCommand.Create(Save, canSave);

Идея состоит в том, что он переоценивает каждый раз, когда изменяется активное свойство. Не уверен, что это вообще лучшее решение (поэтому любые предложения по его улучшению приветствуются), но определенно я не могу добавить IsBusy к картинке, чтобы переоценить предложение Select (включая состояние IsBusy), когда IsBusy изменения.


  • Кажется, я нашел способ сделать это. var canSave = this.WhenAnyObservable(x => x.MyObjectsList.ItemChanged, x => x.MyObject.Changed) .Where(x => x.PropertyName == Active).Select(_ => Unit.Default) .Merge (this.WhenAny(x=›x.IsBusy, _ =› Unit.Default)) .Select(_ =› (MyObjectsList.Any(x =› x.Active) || MyObject.Active) && IsBusy == false) ; 31.07.2018
  • Есть лучшее решение, использующее .Switch(). Я постараюсь поднять его позже. 31.07.2018
  • WhenAnyObservable() в этом случае действует как переключатель. Select() после Merge() можно исключить, используя CombineLatest() вместо этих двух операторов. 01.08.2018

Ответы:


1

Я думаю, что ваш ответ можно упростить.

var itemChangedObs = this.WhenAnyValue(x => x.MyObject.Active);
var isBusyObs = this.WhenAnyValue(x => x.IsBusy);
var listItemChangedObs = this.WhenAnyObservable(x => x.MyObectsList.ItemChanged).Where(x => x.PropertyName == "Active").Select(_ => MyObjectsList.Any(x => x.Active)).StartsWith(false)
var canRunCommand = itemChangedObs.CombineLatest(listItemChangedObs, isBusyObs, (itemActive, listItemActive, isBusy) => (itemActive || listItemActive) && !isBusy);

В этой версии по существу используется CombineLatest, который принимает лямбда того значения, которое вы хотели бы получить после объединения двух наблюдаемых вместе.

CombineLatest() не создает значения до тех пор, пока оба Observable не выдали значение, поэтому listItemChanged имеет StartsWith(false) впереди.

WhenAnyValue() всегда будет по умолчанию выдавать значение по умолчанию (T) в качестве его начального значения, поэтому вам не нужен StartsWith с этими операторами.

31.07.2018
  • Нравится подход и читабельность. Но listItemChangedObs помечается только в том случае, если измененный объект активен, а не в том случае, если активен какой-либо элемент. Поэтому я бы изменил конец строки 3, чтобы сказать .Select(_ => MyObjectsList.Any(x => x.Active).StartsWith(false); 01.08.2018
  • И логика CombineLatest должна быть (itemActive || listItemActive) && !isBusy 01.08.2018

  • 2

    Вот еще одна альтернатива, которая позволяет избежать необходимости в MyObjectList.Any(), которая имеет сложность O (n). Это решение немного сложнее, но имеет потенциал для немного большей эффективности. Это то же самое, что и подход Гленна CombineLatest, за исключением того, как вычисляется его наблюдаемый объект listItemChangedObs. Эта версия поддерживает общее количество активных объектов в списке. Таким образом, он должен делать только +1 или -1 каждый раз, когда запускается ItemChanged. Затем он просто проверяет, больше ли оно 0.

    public MyViewModel()
    {
        var itemChangedObs = this.WhenAnyValue(x => x.MyObject.Active);
        var isBusyObs = this.WhenAnyValue(x => x.IsBusy);
    
        // Recalculate the # of active objects each time ObjectList is reassigned.
        var activeListItemCountInitializedObs = this
            .WhenAnyValue(x => x.ObjectList)
            .Select(
                list =>
                {
                    // Return 0 if ObjectList is null.
                    return list == null ? Observable.Return(0) : list
                        .ToObservable()
                        // Otherwise, increment by 1 for each active object.
                        .Select(x => x.Active ? 1 : 0)
                        // We use Aggregate, which is a single value sequence, because
                        // we're only interested in the final result.
                        .Aggregate((acc, current) => acc + current);
                })
            // We no longer need the inner observable from the last time active item count
            // was initialized. So unsubscribe from that one and subscribe to this most recent one.
            .Switch();
    
        var activeListItemCountChangedObs = this
            .WhenAnyObservable(x => x.ObjectList.ItemChanged)
            .Where(x => x.PropertyName == "Active")
            // Increment or decrement the number of active objects in the list.
            .Select(x => x.Sender.Active ? 1 : -1);
    
        // An IObservable<bool> that signals if *any* of objects in the list are active.
        var anyListItemsActiveObs = activeListItemCountInitializedObs
            .Select(
                // Use the initialized count as the starting value for the Scan accumulator.
                initialActiveCount =>
                {
                    return activeListItemCountChangedObs
                        .Scan(initialActiveCount, (acc, current) => acc + current)
                        // Return true if one or more items are active.
                        .Select(x => x > 0)
                        .StartWith(initialActiveCount > 0);
                })
            // ObjectList was completely reassigned, so the previous Scan accumulator is
            // no longer valid. So we "reset" it by "switching" to the new one.
            .Switch();
    
        var canRunCommand = itemChangedObs
            .CombineLatest(
                anyListItemsActiveObs,
                isBusyObs,
                (itemActive, listItemActive, isBusy) => (itemActive || listItemActive) && !isBusy);
    
        Save = ReactiveCommand.CreateFromObservable(() => Observable.Return(Unit.Default), canRunCommand);
    }
    

    А вот модульный тест, который прошел, когда я запустил код. По сути, он проверяет, сколько раз ReactiveCommand изменяет состояние CanExecute, и если оно истинно или ложно, каждый раз, когда изменяется одна из переменных.

    [Fact]
    public void TestMethod1()
    {
        var objectList = new ReactiveList<IMyObject>(
            initialContents: new[] { new MyObject(), new MyObject() },
            resetChangeThreshold: 0.3,
            scheduler: ImmediateScheduler.Instance);
    
        objectList.ChangeTrackingEnabled = true;
    
        IMyViewModel myViewModel = new MyViewModel
        {
            ObjectList = objectList,
            MyObject = new MyObject()
        };
    
        var canExecute = myViewModel.Save
            .CanExecute
            .CreateCollection(scheduler: ImmediateScheduler.Instance);
    
        Assert.Equal(1, canExecute.Count);
        Assert.False(canExecute[0]);
    
        myViewModel.ObjectList[0].Active = true;
        Assert.Equal(2, canExecute.Count);
        Assert.True(canExecute[1]);
    
        myViewModel.MyObject.Active = true;
        Assert.Equal(2, canExecute.Count);
    
        myViewModel.IsBusy = true;
        Assert.Equal(3, canExecute.Count);
        Assert.False(canExecute[2]);
    
        myViewModel.IsBusy = false;
        Assert.Equal(4, canExecute.Count);
        Assert.True(canExecute[3]);
    
        myViewModel.MyObject.Active = false;
        Assert.Equal(4, canExecute.Count);
    
        var object1 = new MyObject { Active = true };
        var object2 = new MyObject { Active = true };
        myViewModel.ObjectList = new ReactiveList<IMyObject>(
            initialContents: new[] { object1, object2 },
            resetChangeThreshold: 0.3,
            scheduler: ImmediateScheduler.Instance);
    
        Assert.Equal(4, canExecute.Count);
    
        object1 = new MyObject { Active = false };
        object2 = new MyObject { Active = false };
        myViewModel.ObjectList = new ReactiveList<IMyObject>(
            initialContents: new[] { object1, object2 },
            resetChangeThreshold: 0.3,
            scheduler: ImmediateScheduler.Instance);
    
        Assert.Equal(5, canExecute.Count);
        Assert.False(canExecute[4]);
    }
    
    01.08.2018
    Новые материалы

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

    Лицензии с открытым исходным кодом: руководство для разработчиков и создателей
    В динамичном мире разработки программного обеспечения открытый исходный код стал мощной парадигмой, способствующей сотрудничеству, инновациям и прогрессу, движимому сообществом. В основе..

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..


    Для любых предложений по сайту: [email protected]