Exemplo n.º 1
0
        private IDisposable Subscribe(IObserver <CollectionNotification <T> > observer, bool startWithExisting)
        {
            Contract.Requires(observer != null);
            Contract.Ensures(Contract.Result <IDisposable>() != null);

            lock (gate)
            {
                if (isDisposed)
                {
                    return(Observable
                           .Throw <CollectionNotification <T> >(new ObjectDisposedException("ListSubject<T>"))
                           .Subscribe(observer));
                }

                if (exception != null)
                {
                    return(Observable
                           .Throw <CollectionNotification <T> >(exception)
                           .Subscribe(observer));
                }

                if (startWithExisting)
                {
                    IList <T> clonedList = list.ToList().AsReadOnly();

                    observer.OnNext(CollectionNotification.CreateExists(clonedList));
                }

                return(subject.Subscribe(observer));
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Returns an observable sequence of collection notifications that represent changes to the specified <paramref name="key"/>,
        /// starting with the existing value if the dictionary already contains the <paramref name="key"/>.
        /// </summary>
        /// <param name="key">The key for which changes are to be observed.</param>
        /// <returns>
        /// An observable sequence of collection notifications that represent changes to the specified <paramref name="key"/>,
        /// starting with the existing value if the dictionary already contains the <paramref name="key"/>.
        /// </returns>
        public IObservable <CollectionNotification <KeyValuePair <TKey, TValue> > > Changes(TKey key)
        {
            return(Observable.Create <CollectionNotification <KeyValuePair <TKey, TValue> > >(
                       observer =>
            {
                lock (gate)
                {
                    if (isDisposed)
                    {
                        return Observable
                        .Throw <CollectionNotification <KeyValuePair <TKey, TValue> > >(new ObjectDisposedException("DictionarySubject<TKey, TValue>"))
                        .Subscribe(observer);
                    }

                    if (exception != null)
                    {
                        return Observable
                        .Throw <CollectionNotification <KeyValuePair <TKey, TValue> > >(exception)
                        .Subscribe(observer);
                    }

                    if (dictionary.Contains(key))
                    {
                        observer.OnNext(CollectionNotification.CreateExists(dictionary[key]));
                    }

                    var comparer = dictionary.Comparer;

                    return subject
                    .Where(change => change.HasValue && comparer.Equals(change.Value.Key, key))
                    .Subscribe(observer);
                }
            }));
        }
        public static ReadOnlyDictionarySubject <TKey, TResult> Collect <TSource, TKey, TResult>(
            this IObservable <TSource> existing,
            Func <TSource, TKey> keySelector,
            IObservable <CollectionModification <KeyValuePair <TKey, TSource> > > changes,
            Func <IObservable <CollectionNotification <KeyValuePair <TKey, TSource> > >, IObservable <CollectionModification <KeyValuePair <TKey, TResult> > > > selector,
            IEqualityComparer <TKey> comparer)
        {
            Contract.Requires(existing != null);
            Contract.Requires(keySelector != null);
            Contract.Requires(changes != null);
            Contract.Requires(selector != null);
            Contract.Requires(comparer != null);
            Contract.Ensures(Contract.Result <ReadOnlyDictionarySubject <TKey, TResult> >() != null);

            return(new ReadOnlyDictionarySubject <TKey, TResult>(
                       Collect(
                           d => new DictionarySubject <TKey, TResult>(d, comparer),
                           existing,
                           changes,
                           keySelector,
                           pair => pair.Key,
                           (key, value) => CollectionNotification.CreateExists(new KeyValuePair <TKey, TSource>(key, value)),
                           selector,
                           comparer)));
        }
Exemplo n.º 4
0
        private void SetInternal(KeyValuePair <TKey, TValue> newPair)
        {
            if (dictionary.Contains(newPair.Key))
            {
                int index = dictionary.IndexOf(newPair.Key);

                Contract.Assume(index >= 0);

                var oldPair = dictionary[index];

                dictionary[index] = newPair;

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

#if !PORT_40
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newPair, oldPair, index));
#endif

                subject.OnNext(CollectionNotification.CreateOnReplaced(oldPair, newPair));
            }
            else
            {
                AddInternal(newPair);
            }
        }
Exemplo n.º 5
0
        private IDisposable Subscribe(IObserver <CollectionNotification <KeyValuePair <TKey, TValue> > > observer, bool startWithExisting)
        {
            Contract.Requires(observer != null);
            Contract.Ensures(Contract.Result <IDisposable>() != null);

            lock (gate)
            {
                if (isDisposed)
                {
                    return(Observable
                           .Throw <CollectionNotification <KeyValuePair <TKey, TValue> > >(new ObjectDisposedException("DictionarySubject<TKey, TValue>"))
                           .SubscribeSafe(observer));
                }

                if (exception != null)
                {
                    return(Observable
                           .Throw <CollectionNotification <KeyValuePair <TKey, TValue> > >(exception)
                           .SubscribeSafe(observer));
                }

                if (startWithExisting)
                {
                    var clonedList = dictionary.ToList().AsReadOnly();

                    observer.OnNext(CollectionNotification.CreateDictionaryExists(clonedList));
                }

                return(subject.SubscribeSafe(observer));
            }
        }
Exemplo n.º 6
0
        protected void Page_Load(object sender, EventArgs e)
        {
            // Get collection id
            Int32 id = Convert.ToInt32(Request["id"]);

            if (id != 0)
            {
                try
                {
                    // Create Payments helper
                    PaymentsHelper ph = new PaymentsHelper();

                    // Create a token for the API's calls
                    Token token = ph.CreateAccessToken(Properties.Settings.Default.ClientId, Properties.Settings.Default.ClientSecret);
                    ph.AccessToken = token.AccessToken;

                    // Get Collection Notification
                    CollectionNotification cn = ph.GetCollectionNotification(id);

                    // Here goes your code: do something with the notification!
                    // Remember: IPN system waits for your reply about 500ms. If this method times out that threshold it will retry the
                    // notification again. So prepare your code to be fast enough and/or to support retries (ask if the collection was
                    // already processed!).
                    // In this example: Show collection's json
                    Response.Write(cn.ToJSON().ToString());
                }
                catch (Exception ex)
                { }
            }
        }
Exemplo n.º 7
0
        /// <summary>
        /// Gets or sets the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to get or set.</param>
        /// <returns>The element at the specified index.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the list.</exception>
        public T this[int index]
        {
            get
            {
                lock (gate)
                {
                    EnsureNotDisposed();
                    EnsureNotFaulted();

                    Contract.Assume(index < list.Count);

                    return(list[index]);
                }
            }
            set
            {
                lock (gate)
                {
                    EnsureNotDisposed();

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

                        T previous = list[index];

                        list[index] = value;

                        subject.OnNext(CollectionNotification.CreateOnReplaced(previous, value));
                    }
                }
            }
        }
Exemplo n.º 8
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;
                    }
                }
            }
        }
Exemplo n.º 9
0
        public void Add(T item)
        {
            lock (gate)
            {
                EnsureNotDisposed();

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

                    subject.OnNext(CollectionNotification.CreateOnAdded(item));
                }
            }
        }
Exemplo n.º 10
0
        public void Clear()
        {
            lock (gate)
            {
                EnsureNotDisposed();

                if (EnsureNotStopped())
                {
                    list.Clear();

                    subject.OnNext(CollectionNotification.CreateOnCleared <T>());
                }
            }
        }
Exemplo n.º 11
0
        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> >());
        }
Exemplo n.º 12
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));
                }
            }
        }
Exemplo n.º 13
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));
        }
Exemplo n.º 14
0
        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));
                }
            }
        }
Exemplo n.º 15
0
        /// <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);
        }
        /// <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();
                }
            }));
        }
Exemplo n.º 17
0
        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));
        }
Exemplo n.º 18
0
        public static ReadOnlyListSubject <TResult> Collect <TSource, TResult>(
            this IObservable <TSource> existing,
            IObservable <CollectionModification <TSource> > changes,
            Func <IObservable <CollectionNotification <TSource> >, IObservable <CollectionModification <TResult> > > selector,
            IEqualityComparer <TSource> comparer)
        {
            Contract.Requires(existing != null);
            Contract.Requires(changes != null);
            Contract.Requires(selector != null);
            Contract.Requires(comparer != null);
            Contract.Ensures(Contract.Result <ReadOnlyListSubject <TResult> >() != null);

            return(new ReadOnlyListSubject <TResult>(
                       Collect(
                           d => new ListSubject <TResult>(d),
                           existing,
                           changes,
                           k => k,
                           k => k,
                           (k, v) => CollectionNotification.CreateExists(v),
                           selector,
                           comparer)));
        }
Exemplo n.º 19
0
        protected void Page_Load(object sender, EventArgs e)
        {
            // Get collection id
            Int32 id = Convert.ToInt32(Request["id"]);

            if (id != 0)
            {
                try
                {
                    // Create Payments helper
                    PaymentsHelper ph = new PaymentsHelper();

                    // Create a token for the API's calls
                    // Remember 1st!
                    // Change the property settings (Settings.settings) for the following variables:
                    // Your ClientId, Your ClientSecret
                    Token token = AuthHelper.CreateAccessToken(Properties.Settings.Default.ClientId, Properties.Settings.Default.ClientSecret);
                    ph.AccessToken = token.AccessToken;

                    // Get Collection Notification
                    CollectionNotification cn = ph.GetCollectionNotification(id);

                    // Here goes your code: do something with the notification!
                    // Remember: IPN system waits for your reply about 500ms. If this method times out that threshold it will retry the
                    // notification again. So prepare your code to be fast enough and/or to support retries (eg., ask if the collection was
                    // already processed!).
                    // This example just shows collection's attribute values
                    Label1.Text = "<b>currency id:</b> " + cn.Collection.CurrencyId + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector id:</b> " + cn.Collection.Collector.Id.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector first name:</b> " + cn.Collection.Collector.FirstName + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector last name:</b> " + cn.Collection.Collector.LastName + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector nickname:</b> " + cn.Collection.Collector.Nickname + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector email:</b> " + cn.Collection.Collector.Email + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector phone areacode:</b> " + cn.Collection.Collector.Phone.AreaCode + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector phone number:</b> " + cn.Collection.Collector.Phone.Number + "<br/>";
                    Label1.Text = Label1.Text + "<b>collector phone extension:</b> " + cn.Collection.Collector.Phone.Extension + "<br/>";
                    Label1.Text = Label1.Text + "<b>date approved:</b> " + cn.Collection.DateApproved.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>date created:</b> " + cn.Collection.DateCreated.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>external reference:</b> " + cn.Collection.ExternalReference + "<br/>";
                    Label1.Text = Label1.Text + "<b>finance charge:</b> " + cn.Collection.FinanceCharge.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>id:</b> " + cn.Collection.Id.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>installments:</b> " + cn.Collection.Installments.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>last modified:</b> " + cn.Collection.LastModified.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>marketplace:</b> " + cn.Collection.Marketplace.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>mercadopago fee:</b> " + cn.Collection.MercadoPagoFee.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>money release date:</b> " + cn.Collection.MoneyReleaseDate.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>net received amount:</b> " + cn.Collection.NetReceivedAmount.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>operation type:</b> " + cn.Collection.OperationType + "<br/>";
                    Label1.Text = Label1.Text + "<b>order id:</b> " + cn.Collection.OrderId + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer id:</b> " + cn.Collection.Payer.Id.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer first name:</b> " + cn.Collection.Payer.FirstName + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer last name:</b> " + cn.Collection.Payer.LastName + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer nickname:</b> " + cn.Collection.Payer.Nickname + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer email:</b> " + cn.Collection.Payer.Email + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer phone areacode:</b> " + cn.Collection.Payer.Phone.AreaCode + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer phone number:</b> " + cn.Collection.Payer.Phone.Number + "<br/>";
                    Label1.Text = Label1.Text + "<b>payer phone extension:</b> " + cn.Collection.Payer.Phone.Extension + "<br/>";
                    Label1.Text = Label1.Text + "<b>payment type:</b> " + cn.Collection.PaymentType + "<br/>";
                    Label1.Text = Label1.Text + "<b>reason:</b> " + cn.Collection.Reason + "<br/>";
                    Label1.Text = Label1.Text + "<b>released:</b> " + cn.Collection.Released.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>shipping cost:</b> " + cn.Collection.ShippingCost.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>site id:</b> " + cn.Collection.SiteId + "<br/>";
                    Label1.Text = Label1.Text + "<b>status:</b> " + cn.Collection.Status + "<br/>";
                    Label1.Text = Label1.Text + "<b>status detail:</b> " + cn.Collection.StatusDetail + "<br/>";
                    Label1.Text = Label1.Text + "<b>total paid amount:</b> " + cn.Collection.TotalPaidAmount.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>transaction amount:</b> " + cn.Collection.TransactionAmount.ToString() + "<br/>";
                    Label1.Text = Label1.Text + "<b>json:</b> " + cn.ToJSON().ToString();
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }
Exemplo n.º 20
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;
            }
        }
Exemplo n.º 21
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);
        }