Ejemplo n.º 1
0
        /// <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;
                    }
                }
            }
        }
Ejemplo n.º 2
0
        public void Add(T item)
        {
            lock (gate)
            {
                EnsureNotDisposed();

                if (EnsureNotStopped())
                {
                    list.Add(item);

                    subject.OnNext(CollectionNotification.CreateOnAdded(item));
                }
            }
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Inserts an item to the list at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="item">The object to insert into the list.</param>
        public void Insert(int index, T item)
        {
            lock (gate)
            {
                EnsureNotDisposed();

                if (EnsureNotStopped())
                {
                    Contract.Assume(index <= list.Count);

                    list.Insert(index, item);

                    subject.OnNext(CollectionNotification.CreateOnAdded(item));
                }
            }
        }
Ejemplo n.º 4
0
        private void AddInternal(KeyValuePair <TKey, TValue> pair)
        {
            int index = dictionary.Count;

            dictionary.Add(pair);

            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Keys"));
            OnPropertyChanged(new PropertyChangedEventArgs("Values"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));

#if !PORT_40
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, pair, index));
#endif

            subject.OnNext(CollectionNotification.CreateOnAdded(pair));
        }
Ejemplo n.º 5
0
        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;
            }
        }
Ejemplo n.º 6
0
        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);
        }