/// <summary>
        /// Allows you to register a delegate that is called when the tracked objects change.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="onChangedDelegate">The on changed delegate.</param>
        public static void WhenChanged(this IEnumerable <ITrackableObject> source, Action onChangedDelegate)
        {
            TrackingCache cache = new TrackingCache();

            foreach (ITrackableObject item in source)
            {
                WhenChanged(item, onChangedDelegate, cache);
            }
        }
        public static void StartTracking(this IEnumerable <ITrackableObject> source)
        {
            TrackingCache propertyCache = new TrackingCache();

            foreach (ITrackableObject item in source)
            {
                StartTracking(item, propertyCache);
            }
        }
        private static bool ShouldAllowProperty(PropertyInfo property, TrackingCache cache)
        {
            bool result;

            if (cache.AllowedProperties.TryGetValue(property, out result))
            {
                return(result);
            }

            result = ShouldAllowProperty(property);
            cache.AllowedProperties[property] = result;
            return(result);
        }
        internal static void WhenChanged(ITrackableObject item, Action onChangedDelegate, TrackingCache cache)
        {
            if (onChangedDelegate == null)
            {
                throw new ArgumentNullException(nameof(onChangedDelegate));
            }

            TrackingState tracker = GetTracker(item);

            tracker.OnChanged.Add(onChangedDelegate);

            PropertyInfo[] properties;
            if (!cache.Properties.TryGetValue(item.GetType(), out properties))
            {
                properties = item.GetType().GetProperties();
                cache.Properties[item.GetType()] = properties;
            }

            foreach (var property in properties)
            {
                if (property.PropertyType != typeof(TrackingState) && ShouldAllowProperty(property, cache))
                {
                    var value = property.GetValue(item);
                    if (property.PropertyType.IsGenericType && property.PropertyType.IsAssignableTo(typeof(System.Collections.IEnumerable)))
                    {
                        System.Collections.IEnumerable list = (System.Collections.IEnumerable)value;
                        foreach (object subItem in list)
                        {
                            ITrackableObject trackedItem = subItem as ITrackableObject;
                            if (trackedItem != null)
                            {
                                WhenChanged(trackedItem, onChangedDelegate, cache);
                            }
                        }
                    }
                }
            }
        }
        public static void WhenChanged(this ITrackableObject item, Action onChangedDelegate)
        {
            TrackingCache cache = new TrackingCache();

            WhenChanged(item, onChangedDelegate, cache);
        }
        private static TrackingState StartTracking(ITrackableObject source, TrackingCache cache)
        {
            TrackingState tracker = new TrackingState(source);

            PropertyInfo[] properties;
            if (!cache.Properties.TryGetValue(source.GetType(), out properties))
            {
                properties = source.GetType().GetProperties();
                cache.Properties[source.GetType()] = properties;
            }

            foreach (var property in properties)
            {
                if (property.PropertyType != typeof(TrackingState) && ShouldAllowProperty(property, cache))
                {
                    var value = property.GetValue(source);

                    if (property.PropertyType.IsGenericType && property.PropertyType.IsAssignableTo(typeof(System.Collections.IEnumerable)))
                    {
                        System.Collections.IEnumerable list = (System.Collections.IEnumerable)value;


                        List <ITrackableObject> deletedItems = new List <ITrackableObject>();
                        foreach (object item in list)
                        {
                            ITrackableObject trackedItem = item as ITrackableObject;
                            if (trackedItem != null)
                            {
                                if (trackedItem.IsTracking() && trackedItem.IsDeleted())
                                {
                                    deletedItems.Add(trackedItem);
                                    StopTracking(trackedItem);
                                }
                                else
                                {
                                    StartTracking(trackedItem, cache);
                                }
                            }
                        }

                        //Remove any items we detected as having been deleted.
                        IList editableList = list as IList;
                        if (editableList != null)
                        {
                            foreach (var removeItem in deletedItems)
                            {
                                editableList.Remove(removeItem);
                            }
                        }
                    }
                    else if (property.PropertyType.IsAssignableTo(typeof(ITrackableObject)))
                    {
                        ITrackableObject trackedItem = value as ITrackableObject;
                        if (trackedItem != null)
                        {
                            StartTracking(trackedItem, cache);
                        }
                    }

                    tracker.UnmodifiedState.Add(property, value);
                }
            }

            trackedObjects[source] = tracker;
            return(tracker);
        }
        public static TrackingState StartTracking(this ITrackableObject source)
        {
            TrackingCache cache = new TrackingCache();

            return(StartTracking(source, cache));
        }