/// <summary> /// Changes the list according to the specified collection notification. /// </summary> /// <param name="value">A modification that indicates how the list must be changed.</param> public void OnNext(CollectionModification <T> value) { if (value == null) { throw new ArgumentNullException("value"); } lock (gate) { EnsureNotDisposed(); if (EnsureNotStopped()) { IList <T> values; switch (value.Kind) { case CollectionModificationKind.Add: values = value.Values; for (int i = 0; i < values.Count; i++) { var item = values[i]; list.Add(item); subject.OnNext(CollectionNotification.CreateOnAdded(item)); } break; case CollectionModificationKind.Remove: values = value.Values; for (int i = 0; i < values.Count; i++) { var item = values[i]; if (list.Remove(item)) { subject.OnNext(CollectionNotification.CreateOnRemoved(item)); } } break; case CollectionModificationKind.Clear: list.Clear(); subject.OnNext(CollectionNotification.CreateOnCleared <T>()); break; } } } }
public void RemoveAt(int index) { lock (gate) { EnsureNotDisposed(); if (EnsureNotStopped()) { Contract.Assume(index < list.Count); var item = list[index]; list.RemoveAt(index); subject.OnNext(CollectionNotification.CreateOnRemoved(item)); } } }
/// <summary> /// Removes the first occurrence of a specific object from the list. /// </summary> /// <param name="item">The object to remove from the list.</param> /// <returns><see langword="True" /> if <paramref name="item"/> was successfully removed from the list; otherwise, <see langword="false" />. /// This method also returns <see langword="false" /> if <paramref name="item"/> is not found in the list.</returns> public bool Remove(T item) { lock (gate) { EnsureNotDisposed(); if (EnsureNotStopped()) { if (list.Remove(item)) { subject.OnNext(CollectionNotification.CreateOnRemoved(item)); return(true); } } } return(false); }
private void RemoveInternal(TKey key) { Contract.Assume(dictionary.Contains(key)); var index = dictionary.IndexOf(key); Contract.Assume(index >= 0); var pair = dictionary[index]; dictionary.RemoveAt(index); OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Keys")); OnPropertyChanged(new PropertyChangedEventArgs("Values")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); #if !PORT_40 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, pair, index)); #endif subject.OnNext(CollectionNotification.CreateOnRemoved(pair)); }
protected override void Main() { var source = new DictionarySubject <string, CustomValue>(); var monitorKeys = new[] { "A", "C", "D", "E", "F", "G" }; // PropertyChanges doesn't work on KeyValuePair because it doesn't have any property change events. // So we must strip off the key and create a sequence of collection notifications for the values only. using (source .Where( exists: _ => false, onAdded: added => monitorKeys.Contains(added.Key), onReplaced: (replaced, added) => monitorKeys.Contains(replaced.Key) && monitorKeys.Contains(added.Key), onRemoved: removed => monitorKeys.Contains(removed.Key), onCleared: () => false) .Select( _ => null, added => CollectionNotification.CreateOnAdded(added.Value), (replaced, added) => CollectionNotification.CreateOnReplaced(replaced.Value, added.Value), removed => CollectionNotification.CreateOnRemoved(removed.Value), () => null) .PropertyChanges() .Select(e => new { Object = (CustomValue)e.Sender, Property = e.EventArgs.PropertyName }) .Subscribe(value => TraceLine("Changed {0}: {1}", value.Property, value.Object))) { var a = new CustomValue("A"); var b = new CustomValue("B"); var c = new CustomValue("C"); source.Add(a.Name, a); source.Add(b.Name, b); source.Add(c.Name, c); a.Child = new CustomValue("D"); b.Child = new CustomValue("E"); c.Child = new CustomValue("F"); a.Child.Child = new CustomValue("G"); a.Number = 1; b.Number = 1; c.Number = 1; a.Child.Number = 100; b.Child.Number = 101; c.Child.Number = 102; var child = a.Child; var grandchild = child.Child; grandchild.Number = 103; child.Child = null; grandchild.Number = 104; child.Number = 105; a.Child = null; child.Number = 106; source.Remove(a.Name); a.Number = 2; b.Number = 2; c.Number = 2; child.Number = 200; b.Child.Number = 201; c.Child.Number = 202; grandchild.Number = 203; source.Remove(b.Name); source.Remove(c.Name); a.Number = 4; b.Number = 4; c.Number = 4; child.Number = 300; b.Child.Number = 301; c.Child.Number = 302; grandchild.Number = 303; } }
internal static TSubject Collect <TSubject, TExisting, TKey, TNotify, TResult>( Func <IDisposable, TSubject> subjectFactory, IObservable <TExisting> existing, IObservable <CollectionModification <TNotify> > changes, Func <TExisting, TKey> existingKeySelector, Func <TNotify, TKey> changeKeySelector, Func <TKey, TExisting, CollectionNotification <TNotify> > existsNotificationFactory, Func <IObservable <CollectionNotification <TNotify> >, IObservable <CollectionModification <TResult> > > selector, IEqualityComparer <TKey> comparer) where TSubject : ISubject <CollectionModification <TResult>, CollectionNotification <TResult> > { Contract.Requires(subjectFactory != null); Contract.Requires(existing != null); Contract.Requires(changes != null); Contract.Requires(existingKeySelector != null); Contract.Requires(changeKeySelector != null); Contract.Requires(existsNotificationFactory != null); Contract.Requires(selector != null); Contract.Requires(comparer != null); Contract.Ensures(Contract.Result <TSubject>() != null); var disposables = new CompositeDisposable(); var subject = subjectFactory(disposables); Contract.Assume(subject != null); var converterProxy = new Subject <CollectionNotification <TNotify> >(); disposables.Add(converterProxy); var converted = selector(converterProxy.AsObservable()); Contract.Assume(converted != null); disposables.Add(converted.Subscribe(subject)); Action <Exception> onError = error => { converterProxy.OnError(error); subject.OnError(error); }; Action onCompleted = () => { converterProxy.OnCompleted(); subject.OnCompleted(); }; var gate = new object(); var reconciliation = new Dictionary <TKey, bool>(comparer); var changesCompleted = false; /* The changes sequence has precedence over the existing sequence. Changes must be subscribed first and it always indicates * the actual known state of the collection. The existing sequence is simply used to populate the collection with "existing" * items, since the changes sequence doesn't provide that information. If no changes occur, then the final collection will be * the same as the existing sequence. It's also possible for the changes subscription to receive notifications for items that * have not yet been observed by the existing subscription. An OnRemoved notification is automatically dropped until the * item has been added to the collection, either by the existing subscription or by an OnAdded notification from the changes * sequence. There are also a few possible race conditions; in any case, the changes sequence always wins. */ disposables.Add(changes.Subscribe( change => { lock (gate) { if (reconciliation == null) { foreach (var notification in change.ToNotifications()) { converterProxy.OnNext(notification); } } else { TKey key; IList <TNotify> values; switch (change.Kind) { case CollectionModificationKind.Add: values = change.Values; for (int i = 0; i < values.Count; i++) { var value = values[i]; key = changeKeySelector(value); /* * The Removed case sets the item to false to distinguish between two race conditions. The first race condition * is described in the comments for the Remove case. The second race condition is one that might cause the existing * subscription to be notified about an item after it's created, but before the changes subscription is notified. * For this race, since the item already exists in the reconciliation list the changes subscription will not push * it into the subject, which normally prevents duplicate notifications; although in this case, since the item's * value is false, the new OnAdded notification is valid and must be included. */ if (!reconciliation.ContainsKey(key) || !reconciliation[key]) { reconciliation[key] = true; converterProxy.OnNext(CollectionNotification.CreateOnAdded <TNotify>(value)); } } break; case CollectionModificationKind.Remove: values = change.Values; for (int i = 0; i < values.Count; i++) { var value = values[i]; key = changeKeySelector(value); bool remove = reconciliation.ContainsKey(key); /* Even though the item has been removed it stil must be added to the reconciliation dictionary anyway. Adding the * item avoids a race condition between the "changes" and "existing" observables that might cause the existing * subscription to be notified about an item after it has already been removed. Since the item already exists in the * reconciliation list, even though its flag is set to false, the existing subscription will not push it into the subject. * Reconciliation also avoids duplicate OnAdded notifications due to another race condition; although, in this case it * is meant to prevent an invalid OnAdded notification for an item that has already been removed. Assigning the flag * to false, however, causes subsequent Add modifications to be accepted for items that have already been removed. */ reconciliation[key] = false; if (remove) { converterProxy.OnNext(CollectionNotification.CreateOnRemoved <TNotify>(value)); } } break; case CollectionModificationKind.Clear: reconciliation = null; converterProxy.OnNext(CollectionNotification.CreateOnCleared <TNotify>()); break; } } } }, onError, () => { bool completeNow; lock (gate) { changesCompleted = true; completeNow = reconciliation == null; } if (completeNow) { onCompleted(); } })); disposables.Add(existing.Subscribe( value => { lock (gate) { var key = existingKeySelector(value); if (reconciliation != null && !reconciliation.ContainsKey(key)) { reconciliation.Add(key, true); converterProxy.OnNext(existsNotificationFactory(key, value)); } } }, onError, () => { bool completeNow; lock (gate) { reconciliation = null; completeNow = changesCompleted; } if (completeNow) { onCompleted(); } })); return(subject); }