/// <summary>
        /// Creates an observable collection whose content reflects the content of the specified collection, filtered by the specified predicate and mapped by the specified selector.
        /// This collection is kept up-to-date with respect to changes in the original colletion.
        /// </summary>
        public static MyReadOnlyObservableCollection <TResult> WhereSelectLive <T, TResult>(this IReadOnlyObservableCollection <T> collection, Func <T, bool> predicate, Func <T, TResult> selector)
        {
            Contract.Requires(collection != null);
            Contract.Requires(selector != null);

            var result = new ProperObservableCollection <TResult>();

            collection.CollectionChanged += collectionChanged;
            collectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection));
            return(new MyReadOnlyObservableCollection <TResult>(result));

            void collectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                Contract.Requires(sender == collection);

                switch (e.Action)
                {
                case NotifyCollectionChangedAction.Add:
                    result.InsertRange(e.NewStartingIndex, e.NewItems.Cast <T>().Where(predicate).Select(selector));
                    break;

                case NotifyCollectionChangedAction.Remove:
                    result.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    result.Replace(e.OldStartingIndex, e.OldItems.Count, e.NewItems.Cast <T>().Where(predicate).Select(selector));
                    break;

                case NotifyCollectionChangedAction.Move:
                    result.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Reset:
                    result.Clear();
                    break;

                default:
                    throw new ArgumentException("No defined NotifyCollectionChangedEventArgs.Action specified", nameof(e));
                }
            }
        }
        private static void collectionChanged <T, TResult>(NotifyCollectionChangedEventArgs e, ProperObservableCollection <TResult> result, Func <T, TResult> selector)
        {
            Contract.Requires(e != null);
            Contract.Requires(result != null);
            Contract.Requires(selector != null);

            switch (e.Action)
            {
            case NotifyCollectionChangedAction.Add:
                if (e.NewStartingIndex == -1)
                {
                    result.AddRange(e.NewItems.Cast <T>().Select(selector));
                }
                else
                {
                    result.InsertRange(e.NewStartingIndex, e.NewItems.Cast <T>().Select(selector));
                }
                break;

            case NotifyCollectionChangedAction.Remove:
                result.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
                break;

            case NotifyCollectionChangedAction.Replace:
                result.Replace(e.OldStartingIndex, e.OldItems.Count, e.NewItems.Cast <T>().Select(selector));
                break;

            case NotifyCollectionChangedAction.Move:
                result.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
                break;

            case NotifyCollectionChangedAction.Reset:
                result.Clear();
                break;

            default:
                throw new ArgumentException("No defined NotifyCollectionChangedEventArgs.Action specified", nameof(e));
            }
        }
        /// <summary>
        /// Creates an observable collection whose content reflects the content of the specified collection, filtered by the specified predicate and mapped by the specified many selector.
        /// This collection is kept up-to-date with respect to changes in the original colletion.
        /// </summary>
        public static MyReadOnlyObservableCollection <TResult> WhereSelectManyLive <T, TResult>(this ObservableCollection <T> collection, Func <T, bool> predicate, Func <T, IEnumerable <TResult> > selector)
        {
            Contract.Requires(collection != null);
            Contract.Requires(selector != null);

            List <int> elementCountsPerSourceElement = new List <int>();

            int cumulativeElements(int sourceElementIndex)
            {
                return(elementCountsPerSourceElement.Take(sourceElementIndex).Sum());
            }

            var result = new ProperObservableCollection <TResult>();

            collection.CollectionChanged += collectionChanged;
            collectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection));
            return(new MyReadOnlyObservableCollection <TResult>(result));

            void collectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                Contract.Requires(sender == collection);

                switch (e.Action)
                {
                case NotifyCollectionChangedAction.Add:
                {
                    int sourceItemIndex = e.NewStartingIndex;
                    int resultIndex     = cumulativeElements(e.NewStartingIndex);
                    foreach (var newSourceItem in e.NewItems.Cast <T>().Where(predicate))
                    {
                        int originalResultCount = result.Count;
                        result.InsertRange(resultIndex, selector(newSourceItem));
                        int selectedElementCount = result.Count - originalResultCount;

                        resultIndex += selectedElementCount;
                        elementCountsPerSourceElement.Insert(sourceItemIndex++, selectedElementCount);
                    }
                    break;
                }

                case NotifyCollectionChangedAction.Remove:
                {
                    int resultIndex         = cumulativeElements(e.OldStartingIndex);
                    int resultToRemoveCount = elementCountsPerSourceElement.Skip(e.OldStartingIndex).Take(e.OldItems.Count).Sum();
                    result.RemoveRange(resultIndex, resultToRemoveCount);
                    elementCountsPerSourceElement.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
                    break;
                }

                case NotifyCollectionChangedAction.Replace:
                    result.Replace(e.OldStartingIndex, e.OldItems.Count, e.NewItems.Cast <T>().Where(predicate).SelectMany(selector));
                    break;

                case NotifyCollectionChangedAction.Move:
                    result.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Reset:
                    result.Clear();
                    break;

                default:
                    throw new ArgumentException("No defined NotifyCollectionChangedEventArgs.Action specified", nameof(e));
                }
            }
        }