2013-07-11 3 views
1

У меня есть класс 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 

Я хочу остановить обновление положения Боба, если он был удален из коллекции. Как я могу это сделать?

ответ

1

Я обнаружил, что попытка работать с событиями «Добавить и удалить» - это плохая идея, если вы хотите делать такие виды функциональных вещей. Согласование удалений с добавками и обеспечение того, чтобы базовый код тоже делал это, - это большая работа.

Вместо этого я использую скоропортящиеся предметы/коллекции. Я соединяю каждый элемент с пожизненным (маркер отмены), и элемент считается удаленным, когда заканчивается его время жизни. Затем я использую эти времена жизни, проводя другие вещи. Я использую тип коллекции, называемый PerishableCollection<T>, который берет элементы в паре со временем жизни и позволяет просматривать его содержимое как IObservable<Perishable<T>>.

I 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>>>>. Это было бы полезно, если бы не требовалось, чтобы вызывающий управлял временем жизни результата так явно и применим в большем количестве ситуаций. Тем не менее, этот метод намного сложнее писать, потому что вы должны иметь дело с последовательностями, которые не выполняются/завершаются поточно-безопасным способом.

Вот пример использования метода Свести (сделать новое консольное приложение, ссылки на скоропортящиеся коллекции, вставить в указанном выше способе, а также это один):

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 

Надеюсь, этого достаточно, чтобы вы начали с более легкого пути.

+0

Это действительно интересная концепция, спасибо. Мне нужно будет изучить ее дальше, чтобы узнать, применимо ли это в моей ситуации. Хороший блог тоже – AlexFoxGill

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, пожалуйста, дайте мне знать, потому что выше выглядит довольно ужасно!)

+0

Комментируя ваш запрос на рекомендации. Две вещи: 1) Я использовал бы из ... из синтаксиса (для SelectMany), а не из самого внутреннего. Выберите. Во-вторых, когда вложенность становится слишком сложной, рассмотрите ее реорганизацию в отдельный метод. Это становится затруднительным, если вы используете анонимные типы, но, по крайней мере, вы выглядите в этом безопасном. –

+0

Спасибо, солидный совет. Я не часто использую синтаксис запросов, но мне легче писать при вложенности. В моем демо-проекте я фактически использовал анонимные типы, но в реальном сценарии я определенно разделил эти куски на методы и использовал выделенную структуру. – AlexFoxGill