RX:如何将最新值从observable传递到ReactiveCommand

时间:2017-03-28 14:18:08

标签: c# .net reactive-programming observable reactiveui

我想获取IdStream的最新值,并在命令Execute操作中使用它。

public IObservable<Option<Guid>> IdStream { get; }

IdStream = documentStream.OfType<DocumentOpened().Select(x => x.Document.Id.Some())
.Merge(documentStream.OfType<DocumentClosed().Select(x => Option<Guid>.None()));

var saveCommand = ReactiveCommand.Create(() => Save(id), CanExecute);

我曾尝试使用 https://stackoverflow.com/a/31168822/7779560 的答案,并得到了类似的内容:

var saveCommand = ReactiveCommand.Create(() => { }, CanExecute);
saveCommand.WithLatestFrom(IdStream, (_, id) => id)
            .Subscribe(id => Save(id));

它有效,但在这种情况下我不能使用IsExecuting和ThrownExceptions命令的功能(它们仅用于在命令创建期间作为Execute传递的空操作)。

UPD:

执行顺序:

  1. IdStream创建
  2. 创建命令
  3. documentStream处理DocumentOpened事件(获取一些Id值 - 我检查了它)
  4. saveCommand执行
  5. 我怎样才能实现它?

    UPD 2:我还需要等待命令体内的方法(例如SaveAsync)。

3 个答案:

答案 0 :(得分:2)

这对你有用吗?重播将保留最新发布的价值。执行该命令时,它将获取最新值,Take(1)取消订阅,因为您只需要一个值,然后将其推送到Save;

    [Test]
    public void ExecuteUsingLastProducedValue()
    {
        Subject<string> producer = new Subject<string>();
        IObservable<bool> CanExecute = Observable.Return(true);
        IObservable<string> IdStream = producer;
        string SaveCalledWith = String.Empty;

        Func<string, Task> SaveAsync = (id) =>
        {
            SaveCalledWith = id;
            return Task.Delay(0);
        };

        // IdStream creating
         var connectedIdStream =
            IdStream
            .Replay(1);

        connectedIdStream
            .Connect();

        //Command creating
        var command = ReactiveCommand.CreateFromObservable(() =>
        {
            return connectedIdStream
                .Take(1)
                .Do(async id =>
                {
                    await SaveAsync(id);
                });
        }
        , CanExecute);


        //Alternate way
        //Command creating
        //var command = ReactiveCommand.CreateFromObservable(() =>
        //{
        //    return connectedIdStream
        //        .Take(1)
        //        .SelectMany(id => SaveAsync(id).ToObservable());
        //}
        //, CanExecute);


        //documentStream processes DocumentOpened event (get some Id value - I checked it)
        producer.OnNext("something random");
        producer.OnNext("working");

        //At this point Save still hasen't been called so just verifiyng it's still empty
        Assert.AreEqual(String.Empty, SaveCalledWith);

        //trigger execution of command
        command.Execute(Unit.Default).Subscribe();

        //Verified Saved Called With is called
        Assert.AreEqual(SaveCalledWith, "working");
    }

答案 1 :(得分:0)

您想使用Observable.Sample

    [Fact]
    public void ExecuteUsingLastProducedValue()
    {
        Subject<string> producer = new Subject<string>();
        IObservable<bool> CanExecute = Observable.Return(true);
        IObservable<string> IdStream = producer;
        string SaveCalledWith = String.Empty;

        Func<string, Task> SaveAsync = (id) =>
        {
            SaveCalledWith = id;
            return Task.Delay(0);
        };

        // IdStream creating
        var connectedIdStream =
            IdStream
                .Replay(1);

        connectedIdStream
            .Connect();

        //Command creating
        var command = ReactiveCommand.Create(() => { } , CanExecute);
        connectedIdStream.Sample( command )
                         .Subscribe( id => SaveAsync(id) );

        //documentStream processes DocumentOpened event (get some Id value - I checked it)
        producer.OnNext("something random");
        producer.OnNext("working");

        //At this point Save still hasen't been called so just verifiyng it's still empty
        SaveCalledWith.Should().Be( String.Empty );

        //trigger execution of command
        command.Execute(Unit.Default).Subscribe();

        //Verified Saved Called With is called
        SaveCalledWith.Should().Be( "working");
    }

(我用XUnit重写了,因为我手边有这个)

这是一个稍微简化和扩展的测试用例,其中一些代码被我推荐的代码替换。

    [Fact]
    public void ExecuteUsingLastProducedValue()
    {
        var producer = new Subject<string>();
        var canExecute = Observable.Return(true);
        var saveCalledWith = String.Empty;

        void Save(string id) => saveCalledWith = id;

        var rcommand = ReactiveCommand.Create(() => { } , canExecute);

        // When cast to ICommand ReactiveCommand has a
        // more convienient Execute method. No need
        // to Subscribe.
        var command = (ICommand) rcommand; 


        producer
            .Sample( rcommand )
            .Subscribe( Save );

        //documentStream processes DocumentOpened event (get some Id value - I checked it)
        producer.OnNext("something random");
        producer.OnNext("working");

        //At this point Save still hasen't been called so just verifiyng it's still empty
        saveCalledWith.Should().Be( String.Empty );

        //trigger execution of command
        command.Execute( Unit.Default );

        //Verified Saved Called With is called
        saveCalledWith.Should().Be( "working");

        command.Execute( Unit.Default );

        saveCalledWith.Should().Be( "working");

        producer.OnNext("cat");
        saveCalledWith.Should().Be( "working");
        command.Execute( Unit.Default );
        saveCalledWith.Should().Be( "cat");
        producer.OnNext("dog");
        saveCalledWith.Should().Be( "cat");
        command.Execute( Unit.Default );
        saveCalledWith.Should().Be( "dog");
    }

答案 2 :(得分:0)

您可以将流的最新值存储在ObservableAsPropertyHelper<>属性中,并在命令中使用它。

您的班级属性如下所示:

public IObservable<Option<Guid>> IdStream { get; }

private ObservableAsPropertyHelper<Option<Guid>> _currentId;
public Option<Guid> CurrentId => _currentId.Value;

你的构造函数会像这样连接:

IdStream.ToProperty(this, x => x.CurrentId, out _currentId);
var saveCommand = ReactiveCommand.Create(() => Save(CurrentId), CanExecute);

您可能希望为CurrentId属性提供默认值。您可以在ToProperty()电话中执行此操作。