【问题标题】:Switching on IObservable<IEnumerable<IEnumerable<T>>>开启 IObservable<IEnumerable<IEnumerable<T>>>
【发布时间】:2020-03-30 02:33:43
【问题描述】:

我有以下结构:

// source of data
interface IItem
{
    IObservable<string> Changed { get; }
}

interface IItemCollection
{
    List<IItem> Items { get; }
    IObservable<IItem> ItemAdded { get; }
    IObservable<IItem> ItemRemoved { get; }
}

interface IItemCollectionManager
{
    List<IItemCollection> ItemCollectionCollection { get; }
    IObservable<IItemCollection> ItemCollectionAdded { get; }
    IObservable<IItemCollection> ItemCollectionRemoved { get; }
}

// desired result
interface IAggregation
{
    IObservable<string> Changed { get; }
}

这里的目标是让IAggregation 公开单个可观察对象。但是,IItems 可以随时从每个IItemCollection 添加和删除,事实上,IItemCollection 也可以随时从IItemCollectionManager 添加或删除。当然,当添加了这样的IItemCollection 时,Aggregation 也应该从那个中发出值,如果删除了 ItemCollection,我不再需要该集合中 IItems 中的 strings被发射。此外,当Item 被添加到任何IItemCollection 时,来自其Changed observable 的值也应该产生来自IAggregationChanged observable 的值。

现在,当只有一个 IItemCollection 时,解决这个问题相当简单,例如像这样:

class AggregationImpl : IAggregation 
{
    public AggregationImpl(IItemCollection itemCollection)
    {
        var added = itemCollection.ItemAdded
            .Select(_ => itemCollection.Items);
        var removed = itemCollection.ItemRemoved
            .Select(_ => itemCollection.Items);

        Changed = Observable.Merge(added, removed)
            .StartWith(itemCollection.Items)
            .Select(coll => coll.Select(item => item.Changed).Merge())
            .Switch();
    }

    public IObservable<string> Changed { get; }

}

...这里的关键点是我将所有ItemChanged 可观察对象扁平化为带有Merge() 的单个可观察对象,然后,每次添加或删除项目时,我都会重新创建整个Observable 并使用Switch() 退订旧的并订阅新的`。

我觉得扩展以包含 IItemCollectionManager 应该很简单,但我不太确定如何处理它。

【问题讨论】:

  • IItemIAggregation的接口是一样的,是这个意思吗?
  • 可以选择使用SelectMany()吗?
  • @程序。是的,有意的。有第三方希望消费IAggregations observable,但是IAggregation 必须完成从正确的Items 发射值的工作,无论它们何时出现和消失(即使这意味着添加了一个项目或删除,或添加或删除整个项目集合)。
  • @Progman。 System.Reactive.Linq 中的所有内容都是一个选项 - 它以一种有用的方式将它们组装在一起,在这种情况下很棘手。 SelectMany 不能单独工作。我几乎可以肯定我至少需要一个 Switch() 才能让它工作。
  • 你可以使用来自DynamicDataSourceList 吗?由于您正在使用列表和 Rx,您可能想看看这个库。

标签: c# system.reactive


【解决方案1】:

我希望这行得通,或者至少让你走上正确的道路。由于测试似乎相当复杂,因此我将对其进行改进。如果你有一些简单的测试代码,那么我很乐意测试。

首先,我不太喜欢您发布的实现。您正在连接到ItemAddedItemRemoved obeservables,根本不使用数据;您正在从 Items 属性中获取数据。这可能会在糟糕的实现中导致竞争条件,即在属性更新之前发送事件。因此,我创建了自己的实现。我还建议将其放入扩展方法中,因为这样可以让以后的生活更轻松:

public static IObservable<string> ToAggregatedObservable(this IItemCollection itemCollection)
{
    return Observable.Merge(
            itemCollection.ItemAdded.Select(item => (op: "+", item)),
            itemCollection.ItemRemoved.Select(item => (op: "-", item))
        )
        .Scan(ImmutableList<IItem>.Empty.AddRange(itemCollection.Items), (list, t) =>
            t.op == "+"
                ? list.Add(t.item)
                : list.Remove(t.item)
        )
        .Select(l => l.Select(item => item.Changed).Merge())
        .Switch();

}

请暂时原谅魔术字符串,如果您愿意,可以将其变成enum。我们在Scan 内部的ImmutableList 中维护当前项目的状态。当一个项目被添加/删除时,我们更新列表,然后切换 observable。

同样的逻辑可以应用到集合管理器级别:

public static IObservable<string> ToAggregatedObservable(this IItemCollectionManager itemCollectionManager)
{
    return Observable.Merge(
            itemCollectionManager.ItemCollectionAdded.Select(itemColl => (op: "+", itemColl)),
            itemCollectionManager.ItemCollectionRemoved.Select(itemColl => (op: "-", itemColl))
        )
        .Scan(ImmutableList<IItemCollection>.Empty.AddRange(itemCollectionManager.ItemCollectionCollection), (list, t) =>
            t.op == "+"
                ? list.Add(t.itemColl)
                : list.Remove(t.itemColl)
        )
        .Select(l => l.Select(itemColl => itemColl.ToAggregatedObservable()).Merge())
        .Switch();
}

这里我们只是重用了第一种扩展方法,并使用与以前相同的添加/删除然后切换逻辑。

【讨论】:

  • 我意识到在可观察的连接之前添加到集合(或管理器)中的项目存在缺陷。现在通过将它们添加到 .Scan 函数调用的初始化来解决这个问题。
  • 这看起来很有前途@Shlomo。我很快就会尝试这个,但看起来你已经设法弄清楚将最终的Switch() 放在哪里。看起来不错。尽管有针对潜在竞争条件的测试,但我确实同意你的观点,并在昨天更改了我的实现。我确实有一个问题,也许你有意见。很大程度上,这个实现取代了事件。我发现Items 的持续簿记只是杂乱无章,但这些类型本身或不是不可变的(尽管相关部分是)。您认为将它们公开为可观察对象是不好的做法吗?
  • 我并没有真正关注这个问题,抱歉。我认为将什么作为可观察对象公开是一种不好的做法吗?
  • 我特别想的是可变类型。一个 Item 是可变的,Added 和 Removed 的 observables 基本上是在替换等价的事件。似乎有一些共识,对于 IObservable,T 应该是完全不可变的。然而,用 observables 替换所有事件的情况变得困难。不发出可变值(即坚持事件)是铁律吗?
  • Rx 是一个主要用于建模事件的库。事件不会改变。所以我会说这是一个很好的做法,但肯定不是“铁律”。可变T 的主要缺点是,如果您有多个订阅,其中一个订阅可以改变T 对象,这意味着并非所有订阅都看到相同的东西。不过,我想回到的简短答案是,如果您正在对事件进行建模,并且事件不会改变,为什么您有一个可变对象?
猜你喜欢
  • 1970-01-01
  • 2013-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多