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)); } }
/// <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))); }
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); } }
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)); } }
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) { } } }
/// <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)); } } } }
/// <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 Add(T item) { lock (gate) { EnsureNotDisposed(); if (EnsureNotStopped()) { list.Add(item); subject.OnNext(CollectionNotification.CreateOnAdded(item)); } } }
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> /// 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)); } } }
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)); }
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); }
/// <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(); } })); }
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)); }
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))); }
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; } } }
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); }