1

在响应式和响应式扩展中,您可以将多个可观察对象组合为一个。使用诸如 CombineLatest 或 Zip 之类的函数,您可以使生成的 observable 使用一个函数来根据组合 observable 的值计算结果。尽管 CombineLatest 为您提供了一个 observable,它在每次任何组合的 observable 触发时都会向您发出通知,但它仅在所有这些 observable 至少触发一次时才开始工作。我希望它从一开始就发出通知 - 给组合的 observable 一个初始值或其他东西。当然,我可以以某种方式使那些可观察到的火来设置初始值。但有时它并不那么容易,或者它使代码看起来很难看。这是一个例子:我正在使用reactiveui制作一个wpf应用程序。在我的一个视图模型中,我试图通过组合 observables 来创建属性。所以,我有一个具有 NumberToSum 属性的对象集合(ReactiveList),我需要一个属性来显示该属性值的总和。该集合中的对象也是视图模型,并且它们的 NumberToSum 属性可以由用户更改。总和的对象数量由其他属性决定。这就是我尝试这样做的方式:

private readonly ReactiveList<Element> _collection = new ReactiveList<Element>();
private readonly ObservableAsPropertyHelper<int> _sum;
private int _elementsToTake = 0;

public int ElementsToTake
{
    get { return _elementsToTake; }
    set { this.RaiseAndSetIfChanged(ref _elementsToTake, value) }
}

public int Sum
{
    get { return _sum.Value; }
}

public MyViewModel
{
    var elementsToTakeObservable = this
        .WhenAny(x => x.ElementsToTake, x => x.Value);
    _collection.ChangeTrackingEnabled = true;
    //here I'm adding some objects to _collection
    var sumObservable = _collection.ItemChanged
        .CombineLatest(elementsToTakeObservable,
            (_, c) => _collection.Take(c)
                .Aggregate(0, (i, x) => i + x.NumberToSum))).
        .ToProperty(this, x => x.Sum, out _sum, 0);
}

所以,我使用 ReactiveList.ItemChanged 来跟踪 _collection 项目的变化。每次发生更改时,都会重新计算总和。这里的问题是组合成 sumObservable 的 observable 的初始值。我可以通过将值分配给 ElementsToTake 属性来轻松设置 elementsToTakeObservable 的初始值,这将引发属性更改事件和内容。但是对于 _collection.ItemChanged observable 来说,做到这一点并不容易。我需要让 _collection 中的至少一个对象引发 propertychanged 事件,但这看起来很糟糕。是否可以设置这些初始值?或者,也许我这样做完全错了?

4

1 回答 1

7

StartWith will fix your problem. Also, I'm not sure that you want ItemChanged, that signals when an item in the list changes, not when an item is Added or Removed. Here, you probably also want to listen to Changed.

While it's tempting to try to keep a "running total" in this way, it's actually Pretty Hard to do this correctly because of Resets and Moves. An easier approach would be to abuse the fact that CPUs are really fast and just recompute it on every change:

_collection.ChangeTrackingEnabled = true;

Observable.Merge(
         _collection.Changed.Select(_ => Unit.Default), 
         _collection.ItemChanged.Select(_ => Unit.Default))
    .Select(_ => _collection.Sum(x => x.NumberToSum));

Another approach that isn't very good for aggregates like this but can also be super useful, is ActOnEveryObject, which will execute a block on every item in a list, and correctly handles initialization, resets, and moves.

于 2013-11-02T19:01:06.103 回答