代码之家  ›  专栏  ›  技术社区  ›  Sinatr

在使用WhenAnyValue和null传播时,如何避免过时的值?

  •  2
  • Sinatr  · 技术社区  · 2 年前

    我需要监视nullable的更改 Foo 包括…在内 Foo = null 或任何财产的变更,如 Foo.Bar = 123 .

    所以我使用这个语法(类似于 recommended ):

    WhenAnyValue(o => o.Foo, o => o.Foo!.Bar, (foo, bar) => (foo, bar))
    

    订阅两者的更改 Foo Bar 的视图模型:

    public class VM : ReactiveObject
    {
        [Reactive]
        public Foo? Foo { get; set; }
    }
    
    public class Foo : ReactiveObject
    {
        [Reactive]
        public int Bar { get; set; }
    }
    

    但是,这将使用多次运行订阅者 不新鲜的 价值观之后 Foo=空 调用subscriber时使用以前的值 酒吧 以及之后 Foo = new() subscriber被调用两次:以前的值为 酒吧 和正确的一个。这可以用以下代码进行演示:

    var vm = new VM() { Foo = new Foo { Bar = 1 } };
    
    vm.WhenAnyValue(o => o.Foo, o => o.Foo!.Bar, (foo, bar) => (foo, bar))
    .Subscribe(o => Console.WriteLine($"{o.foo?.GetHashCode()} {o.bar}"));
    
    vm.Foo = null; // #1
    
    vm.Foo = new Foo { Bar = 2 }; // #2
    

    输出如下所示:

    58225482 1
     1                   << stale Bar
    32347029 1           << stale Bar
    32347029 2           << extra call
    

    相反,我想要这样的东西:

    58225482 1
     0
    32347029 2
    

    换句话说,我不想 不新鲜的 值和额外的订户呼叫(如果可能的话)。

    这个 Foo == null 检查内部订阅者将解决#1。但对于这样的问题,也许有不同的rx解决方案?或者以某种方式将空校验移出订阅者之外也是可以的?

    至于#2,我只知道存储以前的值的解决方法 Foo 如果上一个值为,则忽略第一个调用 null 。同样,这是一种程序化的编程方式。在rx编程世界中应该如何做到这一点?

    0 回复  |  直到 2 年前
        1
  •  0
  •   Sinatr    2 年前

    为了更清楚地说明问题,我增加了数字 Foo 属性:

    public class Foo : ReactiveObject
    {
        [Reactive]
        public int Bar { get; set; }
        [Reactive]
        public int Baz { get; set; }
        [Reactive]
        public int Qux { get; set; }
    }
    

    然后的输出

    var vm = new VM();
    
    vm.WhenAnyValue(o => o.Foo, o => o.Foo!.Bar, o => o.Foo!.Baz, o => o.Foo!.Qux,
        (foo, bar, baz, qux) => (foo, bar, baz, qux)).Subscribe(o =>
    {
        Console.WriteLine($"{o.foo?.GetHashCode()} {o.bar} {o.baz} {o.qux}");
    });
    
    vm.Foo = new();
    vm.Foo = null;
    vm.Foo = new Foo { Bar = 1, Baz = 2, Qux = 3 };
    vm.Foo = null;
    vm.Foo = new();
    

    将包含许多过时的状态:

    30631159 0 0 0
     0 0 0
    56680499 0 0 0
    56680499 1 0 0
    56680499 1 2 0
    56680499 1 2 3
     1 2 3
    6444509 1 2 3
    6444509 0 2 3
    6444509 0 0 3
    6444509 0 0 0
    

    以下是订阅者的固定版本:

    {
        // discard stale state
        if (o.foo != null && (o.bar != o.foo.Bar || o.baz != o.foo.Baz || o.qux != o.foo.Qux))
            return;
    
        // Console.WriteLine($"{o.foo?.GetHashCode()} {o.bar} {o.baz} {o.qux}");
        // use actual null propagated values!
        Console.WriteLine($"{o.foo?.GetHashCode()} {o.foo?.Bar} {o.foo?.Baz} {o.foo?.Qux}");
    }
    

    现在输出如预期,每次更改一行:

    30631159 0 0 0
    
    56680499 1 2 3
    
    6444509 0 0 0
    

    换句话说:

    • 订阅者到null的传播将总是需要检查以丢弃过时的状态;
    • null传播值不应在订阅者内部使用,它们的目的是检测其单独的更改,例如当 vm.Foo.Bar = 123;

    EDIT:第二条语句是不正确的,有了丢弃,就不再有过时的状态,并且值是正确的,除非订阅者需要很长时间才能完成,但这是 another story .

        2
  •  0
  •   Lukasz Szczygielek    2 年前

    .WhenAnyValue 对作出反应 任何 可观察的变化,它通过收集所有观察到的值来编写代码。

    如果你只感兴趣 Bar , Baz , Qux ,则可以省略 Foo ,您将收到更少的通知:

    vm.WhenAnyValue(o => o.Foo!.Bar, o => o.Foo!.Baz, o => o.Foo!.Qux,
            (bar, baz, qux) => (bar, baz, qux))
        .Subscribe(o =>
    {
        Console.WriteLine($"{o.bar} {o.baz} {o.qux}");
    });
    
    // output
    0 0 0
    1 0 0
    1 2 0
    1 2 3
    0 2 3
    0 0 3
    0 0 0
    
    

    根据您的预期输出,您似乎只想跟踪 Foo 改变

    vm.WhenAnyValue(o => o.Foo)
        .Where(o => o != null)
        .Subscribe(o => Console.WriteLine($"{o.Bar} {o.Baz} {o.Qux}"));
    
    // output
    0 0 0
    1 2 3
    0 0 0