/// <summary>
        /// Binds the specified <see cref="PropertyChangedEventHandler"/> on _all_ observables (including via <see cref="INotifyCollectionChanged"/>)
        /// reachable via reflection that have a property with the specified name and type, and invokes the handler on those event that match the predicate.
        /// </summary>
        public static IDisposable BindRecursively <TPropertyType>(this INotifyPropertyChanged root,
                                                                  string propertyName,
                                                                  PropertyMutatedEventHandler <TPropertyType> handler)
        {
            return(root.BindRecursively(predicate, handlerWrapper));

            bool predicate(object obj, IPropertyMutatedEventArgs e)
            {
                if (e.PropertyName == propertyName)
                {
                    var propertyType = GetPropertyType(obj.GetType(), propertyName);
                    var result       = typeof(TPropertyType).IsAssignableFrom(propertyType);
                    return(result);
                }
                return(false);
            }

            void handlerWrapper(object sender, IPropertyMutatedEventArgs e)
            {
                handler(sender, (PropertyMutatedEventArgs <TPropertyType>)e);
            }
        }
        /// <summary>
        /// Binds the specified <see cref="PropertyChangedEventHandler"/> on _all_ observables (including via <see cref="INotifyCollectionChanged"/>)
        /// reachable via reflection, and invokes the handler on those event that match the predicate.
        /// </summary>
        /// <param name="predicate"> A predicate for invoking the handler. (not a predicate for hooking the handler). </param>
        public static IDisposable BindRecursively(this INotifyPropertyChanged root,
                                                  Func <object, IPropertyMutatedEventArgs, bool> predicate,
                                                  PropertyMutatedEventHandler handler)
        {
            Contract.Requires(root != null);
            Contract.Requires(predicate != null);
            Contract.Requires(handler != null);

            var allHookedObservables = new Dictionary <object, int>(ReferenceEqualityComparer.Instance);            // integer is reference counts by this dictionary

            addReference(root);

#if DEBUG
            return(new BindRecursivelyDisposable(allHookedObservables, () =>
#else
            return new Disposable(() =>
#endif
            {
                foreach (var obj in allHookedObservables)
                {
                    unhook(obj);
                }
                allHookedObservables.Clear();
            }));



            void hook(object tree)
            {
                foreach (object obj in getEntireObjectTree(tree))
                {
                    var deltaReferenceCount = 0;
                    if (obj is INotifyPropertyChanged container)
                    {
                        container.PropertyChanged += handle;
                        deltaReferenceCount++;
                    }
                    if (obj is INotifyCollectionChanged collection)
                    {
                        collection.CollectionChanged += handleCollectionChanged;
                        deltaReferenceCount++;
                    }
                    if (deltaReferenceCount != 0)
                    {
                        allHookedObservables[obj] = allHookedObservables.GetOrAdd(obj, 0) + deltaReferenceCount;
                    }
                }
            }

            void unhook(object tree)
            {
                Contract.Assume(allHookedObservables.ContainsKey(tree));

                foreach (object obj in getEntireObjectTree(tree))
                {
                    var deltaReferenceCount = 0;
                    if (obj is INotifyPropertyChanged container)
                    {
                        container.PropertyChanged -= handle;
                        deltaReferenceCount--;
                    }
                    if (obj is INotifyCollectionChanged collection)
                    {
                        collection.CollectionChanged -= handleCollectionChanged;
                        deltaReferenceCount--;
                    }
                    if (deltaReferenceCount != 0)
                    {
                        allHookedObservables[obj] += deltaReferenceCount;
                    }
                }
            }

            void addReference(object newReferredObject)
            {
                if (newReferredObject != null && allHookedObservables.ContainsKey(newReferredObject))
                {
                    allHookedObservables[newReferredObject]++;
                }
                else
                {
                    hook(newReferredObject);
                }
            }

            void removeReference(object oldReferredObject)
            {
                if (oldReferredObject != null && allHookedObservables.ContainsKey(oldReferredObject))
                {
                    if (--allHookedObservables[oldReferredObject] == 0)
                    {
                        unhook(oldReferredObject);
                    }
                }
            }

            void updateReferences(object oldReferredObject, object newReferredObject)
            {
                if (ReferenceEquals(oldReferredObject, newReferredObject))
                {
                    return;
                }

                addReference(newReferredObject);
                removeReference(oldReferredObject);
            }

            void handle(object sender, PropertyChangedEventArgs originalE)
            {
                if (originalE is IPropertyMutatedEventArgs e)
                {
                    updateReferences(e.OldValue, e.NewValue);
                    if (predicate(sender, e))
                    {
                        handler.Invoke(sender, e);
                    }
                }
            }

            void handleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                switch (e.Action)
                {
                case NotifyCollectionChangedAction.Add:
                    foreach (var newObj in e.NewItems)
                    {
                        addReference(newObj);
                    }
                    break;

                case NotifyCollectionChangedAction.Reset:
                case NotifyCollectionChangedAction.Remove:
                    foreach (var newObj in e.OldItems)
                    {
                        removeReference(newObj);
                    }
                    break;

                case NotifyCollectionChangedAction.Replace:
                    foreach (var newObj in e.NewItems)
                    {
                        addReference(newObj);
                    }
                    foreach (var newObj in e.OldItems)
                    {
                        removeReference(newObj);
                    }
                    break;

                case NotifyCollectionChangedAction.Move:
                    break;

                default:
                    throw new InvalidOperationException();
                }
            }
        }
 /// <summary> Invokes the specified handler when a property on a property changes, or when the source changes,
 /// and, if the current value differs from the specified default value, at the end of this call. </summary>
 /// <param name="propertyName"> The name of the property on the source. </param>
 /// <param name="propertyOnPropertyName"> The name of the property on the property on the source. </param>
 /// <param name="handler"> The handler to be invoked at the moments listed above. </param>
 /// <param name="defaultValue"> The value that is the default of the property on the property (used as old value when the property containing that property is null). </param>
 public static void Bind <T>(this INotifyPropertyChanged source, string propertyName, string propertyOnPropertyName, PropertyMutatedEventHandler <T> handler, T defaultValue)
 {
     Bind(source, propertyName, propertyOnPropertyName, (sender, e) => handler(sender, (PropertyMutatedEventArgs <T>)e), defaultValue, typeof(T));
 }
 /// <summary> Invokes the specified handler when a property on a property changes, or when the source changes,
 /// and, if the current value differs from the default value, at the end of this call. </summary>
 /// <param name="propertyName"> The name of the property on the source. </param>
 /// <param name="propertyOnPropertyName"> The name of the property on the property on the source. </param>
 /// <param name="handler"> The handler to be invoked at the moments listed above. </param>
 public static void Bind <T>(this INotifyPropertyChanged source, string propertyName, string propertyOnPropertyName, PropertyMutatedEventHandler <T> handler)
 {
     // this overload makes use of the feature of the other overload to correct the default value if it is null to the default
     Bind(source, propertyName, propertyOnPropertyName, (sender, e) => handler(sender, (PropertyMutatedEventArgs <T>)e), null);
 }