/// <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 Clear() { lock (gate) { EnsureNotDisposed(); if (EnsureNotStopped()) { list.Clear(); subject.OnNext(CollectionNotification.CreateOnCleared <T>()); } } }
private void ClearInternal() { dictionary.Clear(); OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Keys")); OnPropertyChanged(new PropertyChangedEventArgs("Values")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); #if !PORT_40 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); #endif subject.OnNext(CollectionNotification.CreateOnCleared <KeyValuePair <TKey, TValue> >()); }
/// <summary> /// Converts <see cref="INotifyCollectionChanged.CollectionChanged"/> events into an observable sequence of <see cref="CollectionNotification{T}"/>. /// </summary> /// <typeparam name="T">The object that provides notification information.</typeparam> /// <param name="source">An implementation of <see cref="INotifyCollectionChanged"/> that raises events when a collection changes.</param> /// <remarks> /// An <see cref="NotifyCollectionChangedAction.Add"/> event is projected into zero or more <see cref="CollectionNotificationKind.OnAdded"/> notifications. /// A <see cref="NotifyCollectionChangedAction.Remove"/> event is projected into zero or more <see cref="CollectionNotificationKind.OnRemoved"/> notifications. /// A <see cref="NotifyCollectionChangedAction.Move"/> event is projected into zero or more <see cref="CollectionNotificationKind.OnReplaced"/> notifications /// where <see cref="CollectionNotification{T}.Value"/> and <see cref="CollectionNotification{T}.ReplacedValue"/> refer to the same value. /// A <see cref="NotifyCollectionChangedAction.Replace"/> event is projected into zero or more <see cref="CollectionNotificationKind.OnReplaced"/> notifications. /// A <see cref="NotifyCollectionChangedAction.Reset"/> event is projected into a single <see cref="CollectionNotificationKind.OnCleared"/> notification. /// </remarks> /// <returns>An observable sequence of <see cref="CollectionNotification{T}"/> objects corresponding to raised events.</returns> #else /// <summary> /// Converts <see cref="INotifyCollectionChanged.CollectionChanged"/> events into an observable sequence of <see cref="CollectionNotification{T}"/>. /// </summary> /// <typeparam name="T">The object that provides notification information.</typeparam> /// <param name="source">An implementation of <see cref="INotifyCollectionChanged"/> that raises events when a collection changes.</param> /// <remarks> /// An <see cref="NotifyCollectionChangedAction.Add"/> event is projected into zero or more <see cref="CollectionNotificationKind.OnAdded"/> notifications. /// A <see cref="NotifyCollectionChangedAction.Remove"/> event is projected into zero or more <see cref="CollectionNotificationKind.OnRemoved"/> notifications. /// A <see cref="NotifyCollectionChangedAction.Replace"/> event is projected into zero or more <see cref="CollectionNotificationKind.OnReplaced"/> notifications. /// A <see cref="NotifyCollectionChangedAction.Reset"/> event is projected into a single <see cref="CollectionNotificationKind.OnCleared"/> notification. /// </remarks> /// <returns>An observable sequence of <see cref="CollectionNotification{T}"/> objects corresponding to raised events.</returns> #endif public static IObservable <CollectionNotification <T> > AsCollectionNotifications <T>(this INotifyCollectionChanged source) { Contract.Requires(source != null); Contract.Ensures(Contract.Result <IObservable <CollectionNotification <T> > >() != null); return(Observable.FromEventPattern <NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>( eh => source.CollectionChanged += eh, eh => source.CollectionChanged -= eh) .SelectMany(e => { var args = e.EventArgs; switch (args.Action) { case NotifyCollectionChangedAction.Add: return EnsureSequence <T>(args.NewItems).Select(CollectionNotification.CreateOnAdded).ToObservable(); case NotifyCollectionChangedAction.Remove: return EnsureSequence <T>(args.OldItems).Select(CollectionNotification.CreateOnRemoved).ToObservable(); #if !SILVERLIGHT case NotifyCollectionChangedAction.Move: return EnsureSequence <T>(args.OldItems).Select(value => CollectionNotification.CreateOnReplaced(value, value)).ToObservable(); #endif case NotifyCollectionChangedAction.Replace: return EnsureSequence <T>(args.NewItems) .Zip( EnsureSequence <T>(args.OldItems), (newValue, oldValue) => CollectionNotification.CreateOnReplaced(oldValue, newValue)) .ToObservable(); case NotifyCollectionChangedAction.Reset: return Observable.Return(CollectionNotification.CreateOnCleared <T>()); default: throw new InvalidOperationException(); } })); }
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); }