2013-07-11 4 views
1

J'ai une classe ObservableCollection<T> qui représente une collection en évolution:Suppression des observables nichait de flux

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

Lorsqu'un élément est ajouté ou supprimé, un Subject<IEnumerable<T>> a en interne sa méthode OnNext appelé avec IEnumerable<T> qui est exposée par la méthode Subscribe de IObservableCollection<T>.

J'ai aussi une classe Person:

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

Ce que je veux faire est de produire un flux de IEnumerable<Tuple<string, int>> représentant la position de chaque personne, où une personne est dans la collection. Cela semble relativement simple:

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; 

je peux maintenant vous abonner au flux comme ceci:

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

Et je reçois la sortie désirée:

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 

Le problème se pose quand je veux supprimer une personne de la collection et par conséquent exclure ses mises à jour du flux:

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

Je veux empêcher les mises à jour de position de Bob d'être incluses s'il a été retiré de la collection. Comment puis-je faire ceci?

Répondre

1

J'ai trouvé qu'essayer de travailler avec Ajout et suppression d'événements est une mauvaise idée, si vous voulez faire ce genre de choses fonctionnelles. Faire correspondre les suppressions avec les ajouts, et s'assurer que le code sous-jacent le fait aussi, demande beaucoup de travail.

Ce que je fais à la place est d'utiliser des articles/collections périssables. J'associe chaque élément avec une durée de vie (jeton d'annulation) et l'élément est considéré comme supprimé à la fin de sa durée de vie. Ensuite, j'utilise ces durées de vie lors du câblage d'autres choses. J'utilise un type de collection, appelé PerishableCollection<T>, qui prend les éléments associés à des durées de vie et vous permet de regarder son contenu sous forme de IObservable<Perishable<T>>.

I wrote a blog post about perishable collections, et a publié un nuget library you can reference.

le code est ici qui devrait aplatir une collection périssable des collections périssables:

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; 
} 

Les œuvres ci-dessus en vous inscrivant à recevoir des collections ajoutées à la collection de collections, puis pour chacun de ceux qui souscrivent à recevoir des articles. Les éléments sont placés dans la collection résultante, avec une durée de vie qui se termine lorsque l'élément meurt ou que sa collection meurt. Tous les abonnements meurent lorsque la durée de vie donnée à la méthode meurt.

Une autre façon d'aborder ce problème est d'écrire une méthode pour aplatir un IObservable<Perishable<IObservable<Perishable<T>>>>. Cela aurait l'avantage de ne pas obliger l'appelant à gérer la durée de vie du résultat de manière aussi explicite et applicable dans plus de situations. Cependant, cette méthode est beaucoup plus difficile à écrire car vous devez gérer les séquences qui échouent/se terminent de manière thread-safe.

Voici un exemple d'utilisation de la méthode Aplatir (faire une nouvelle application de la console, des collections de référence périssables, la pâte dans la méthode ci-dessus et celui-ci):

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(); 
} 

Le code devrait afficher:

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

Espérons que cela vous suffira pour commencer un chemin plus facile.

+0

Ceci est un concept très intéressant, merci - je vais devoir l'étudier plus loin pour voir si elle est applicable dans ma situation. Nice blog too – AlexFoxGill

0

La réponse est l'opérateur .Switch. En sélectionnant seulement la plus récente liste des observables abonner, le flux exclut toutes celles qui ne sont pas présents dans la nouvelle version de la collection:

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

(Soit dit en passant, si quelqu'un a des bonnes recommandations sur la mise en forme lors de l'utilisation entre crochets/linq imbriquée syntaxe de requête s'il vous plaît laissez-moi savoir parce que le ci-dessus semble tout à fait horrible!)

+0

Commentant sur votre demande de recommandations. Deux choses: 1) J'utiliserais la syntaxe from ... from (pour SelectMany) plutôt que la plus secrète .Select. Deuxièmement, lorsque l'imbrication devient trop complexe, pensez à la refactoriser en une méthode séparée. Cela devient difficile si vous utilisez des types anonymes, mais vous semblez être en sécurité dans cet exemple au moins. –

+0

Merci, de solides conseils. Je n'utilise pas souvent la syntaxe de requête, mais je trouve qu'il est plus facile d'écrire lors de l'imbrication. Dans mon projet de démo, j'utilisais des types anonymes, mais dans un scénario réel, je diviserai définitivement ces morceaux en méthodes et utiliserai une structure dédiée. – AlexFoxGill

Questions connexes