从流中删除嵌套的observable

时间:2013-07-11 10:31:07

标签: c# system.reactive reactive-programming

我有一个类ObservableCollection<T>代表一个不断变化的集合:

public interface IObservableCollection<T> : IObservable<IEnumerable<T>>
{
    void Add(T item);
    void Remove(T item);
}

在添加或删除某个项目时,Subject<IEnumerable<T>>内部调用了OnNext方法,并使用IEnumerable<T> Subscribe方法通过IObservableCollection<T>方法调用该方法}}

我还有一个班级Person

public interface IPerson
{
    string Name { get; }
    IObservable<int> Position { get; }
}

我想要做的是产生一个IEnumerable<Tuple<string, int>>流,代表每个人的位置,一个人在集合中。这似乎相对简单:

var peopleCollectionStream = new ObservableCollection<IPerson>();

var peoplePositions = from people in peopleCollectionStream
                      from updateList in
                          (from person in people
                           select person.Position.Select(pos => Tuple.Create(person.Name, pos)))
                           .CombineLatest()
                      select updateList;

我现在可以像这样订阅流:

peoplePositions
    .Subscribe(people =>
    {
        Console.WriteLine("Something was updated");
        foreach (var personTuple in people)
            Console.WriteLine("{0} -> {1}", personTuple.Item1, personTuple.Item2);
    });

我得到了所需的输出:

var alice = new Person() { Name = "Alice" };
peopleCollectionStream.Add(alice);        // Alice -> 0
alice.Move(2);                            // Alice -> 2
var bob = new Person() { Name = "Bob" };
peopleCollectionStream.Add(bob);          // Alice -> 2, Bob -> 0
bob.Move(3);                              // Alice -> 2, Bob -> 3

如果我希望从收藏中删除某个人,并因此从流中排除他们的更新,就会出现问题:

peopleCollectionStream.Remove(bob);       // Alice -> 2
bob.Move(4);                              // Alice -> 2, Bob -> 4

如果将Bob的位置更新从集合中删除,我想阻止其被包括在内。我怎么能这样做?

2 个答案:

答案 0 :(得分:1)

我发现尝试使用添加和删除事件是一个坏主意,如果你想做这些功能性事情。将删除与添加相匹配,并确保底层代码也是如此,这是很多工作。

我所做的是使用易腐物品/收藏品。我将每个项目与生命周期(取消令牌)配对,并且该项目在其生命周期结束时被视为已删除。然后我在连接其他东西时使用那些生命周期。我使用了一种名为PerishableCollection<T>的集合类型,它将项目与生命周期配对,并允许您将其内容视为IObservable<Perishable<T>>

wrote a blog post about perishable collections,并发布了nuget library you can reference

这里的代码应该展现易腐变集的易腐变集:

public static PerishableCollection<T> Flattened<T>(this PerishableCollection<PerishableCollection<T>> collectionOfCollections, Lifetime lifetimeOfResult) {
    if (collectionOfCollections == null) throw new ArgumentNullException("collectionOfCollections");

    var flattenedCollection = new PerishableCollection<T>();
    collectionOfCollections.CurrentAndFutureItems().Subscribe(
        c => c.Value.CurrentAndFutureItems().Subscribe(

            // OnItem: include in result, but prevent lifetimes from exceeding source's lifetime
            e => flattenedCollection.Add(
                item: e.Value,
                lifetime: e.Lifetime.Min(c.Lifetime)),

            // subscription to c ends when the c's lifetime ends or result is no longer needed
            c.Lifetime.Min(lifetimeOfResult)),

        // subscription ends when result is no longer needed
        lifetimeOfResult);

    return flattenedCollection;
}

上述工作通过订阅接收集合添加到集合集合,然后为订阅接收项目的每个集合。这些物品被放置在最终的集合中,其寿命在物品死亡或其集合消亡时结束。当给予方法的生命周期终止时,所有订阅都会消失。

解决此问题的另一种方法是编写一个方法来展平IObservable<Perishable<IObservable<Perishable<T>>>>。这样做的好处是不需要调用者明确地管理结果的生命周期,并且适用于更多情况。但是,这种方法写起来要困难得多,因为你必须以线程安全的方式处理序列失败/完成。

以下是使用flatten方法的示例(创建一个新的控制台应用程序,引用易坏的集合,粘贴上述方法以及此方法):

using TwistedOak.Collections;
using TwistedOak.Util;

static void Main() {
    var p = new PerishableCollection<PerishableCollection<string>>();
    var f = p.Flattened(Lifetime.Immortal);
    f.CurrentAndFutureItems().Subscribe(e => {
        Console.WriteLine("{0} added to flattened", e.Value);
        e.Lifetime.WhenDead(() => Console.WriteLine("{0} removed from flattened", e.Value));
    });

    // add some 'c' items to f via p
    var c = new PerishableCollection<string>();
    var cLife = new LifetimeSource();
    c.Add("candy", Lifetime.Immortal);
    p.Add(c, cLife.Lifetime);
    c.Add("cane", Lifetime.Immortal);

    // add some 'd' items to f via p
    var d = new PerishableCollection<string>();
    p.Add(d, Lifetime.Immortal);
    d.Add("door", Lifetime.Immortal);
    d.Add("dock", Lifetime.Immortal);


    // should remove c's items from f via removing c from p
    cLife.EndLifetime();
}

代码应输出:

candy added to flattened
cane added to flattened
door added to flattened
dock added to flattened
candy removed from flattened
cane removed from flattened

希望这足以让你开始走上更轻松的道路。

答案 1 :(得分:0)

答案是.Switch运算符。通过仅选择要订阅的最新可观察列表,该流将排除最新版本集合中不存在的任何内容:

var peoplePositions = (from people in peopleCollectionStream
                       select
                           (from person in people
                            select person.Position
                                .Select(pos => Tuple.Create(person.Name, pos))
                            ).CombineLatest()
                       ).Switch();

(顺便说一下,如果有人在使用括号/嵌套的linq查询语法时对格式有任何好的建议,请告诉我,因为上面看起来非常糟糕!)