internal DeepTracker(TrackRouteConfiguration configuration, object source) { _configuration = configuration; _sourceReference = new WeakReference(source); _propertyChangedRegistry = new Registry <Tuple <PropertyReference, object> >(); _collectionChangedRegistry = new Registry <Tuple <WeakReference, object[]> >(); _collectionChildrenIds = new ConditionalWeakTable <object, string>(); _attachmentObjectRegistry = new Registry <WeakReference>(); _propertyChangedSubscriptions = new DynamicSubscription <PropertyReference, EventHandler>( (changed, handler) => changed.TryAddValueChanged(handler), (changed, handler) => changed.TryRemoveValueChanged(handler)); _collectionChangedSubscriptions = new DynamicSubscription <INotifyCollectionChanged, NotifyCollectionChangedEventHandler>( (changed, handler) => changed.CollectionChanged += handler, (changed, handler) => changed.CollectionChanged -= handler); }
private void AddBranch(Route visitedRoute, PropertyReference reference, TrackRouteConfiguration configuration, IReadOnlyList <int> visitedObjects) { if (reference.Name.Contains(".")) { return; } var propertyRoute = Route.Create(visitedRoute, reference.Name); var match = propertyRoute.MatchPartially(configuration.Route); if (!match) { return; } var lazyValue = new Lazy <object>(() => { try { if (reference.TryGetValue(out var scopedValue)) { return(scopedValue); } } catch { //Nothing } return(NotValidValue); }); if (!configuration.IsAllowed(reference, lazyValue, propertyRoute)) { return; } try { var value = lazyValue.Value; if (value.AreEqual(NotValidValue)) { return; } if (reference.SupportsChangeEvents) { var closureValue = value.WrapValue(); void PropertyChangedHandler(object sender, EventArgs e) { var oldValue = closureValue.UnwrapValue(); object newValue = null; try { reference.TryGetValue(out newValue); } catch { //Nothing } if (ReferenceEquals(oldValue, newValue)) { return; } RemoveBranch(propertyRoute); try { if (newValue != null) { closureValue = newValue.WrapValue(); AddBranch(propertyRoute, newValue, configuration, visitedObjects); } else { closureValue = new WeakReference(null); } if (!Equals(oldValue, newValue)) { var args = new ChangedPropertyEventArgs(propertyRoute, reference, oldValue, newValue); _configuration.Raise(this, args); } } catch { //Nothing } } lock (_propertyChangedRegistry) { var args = new PropertyAttachedEventArgs(propertyRoute, reference, value); _configuration.Raise(this, args); var record = new Tuple <PropertyReference, object>(reference, value); _propertyChangedRegistry.Add(propertyRoute, record); _propertyChangedSubscriptions.Subscribe(reference, PropertyChangedHandler); } } if (value != null) { AddBranch(propertyRoute, value, configuration, visitedObjects); } } catch { //Nothing } }
private void AddBranch(Route visitedRoute, object source, TrackRouteConfiguration configuration, IReadOnlyList <int> visitedObjects) { var sourceHash = source.GetHash(); if (visitedObjects.Contains(sourceHash)) { return; } lock (_attachmentObjectRegistry) { var objectAttachedEventArgs = new ObjectAttachedEventArgs(visitedRoute, source); _configuration.Raise(this, objectAttachedEventArgs); _attachmentObjectRegistry.Add(visitedRoute, new WeakReference(source)); } visitedObjects = visitedObjects.UnionWith(source.GetHash()).ToList(); if (source is IEnumerable collection && !(source is string)) { var sourceItems = collection.Enumerate().ToArray(); if (collection is INotifyCollectionChanged notifyCollection) { var closureSourceItems = new WeakReference(sourceItems.Enumerate().ToList()); void NotifyCollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs args) { var previousItems = closureSourceItems.Target as List <object>; if (args.Action == NotifyCollectionChangedAction.Reset) { var newClosureSourceItems = sender.Enumerate().ToList(); foreach (var item in previousItems.Enumerate()) { if (_collectionChildrenIds.TryGetValue(item, out var id)) { RemoveBranch(Route.Create(visitedRoute, id)); } } closureSourceItems = new WeakReference(newClosureSourceItems); foreach (var item in newClosureSourceItems) { if (item == null) { continue; } if (!_collectionChildrenIds.TryGetValue(item, out var id)) { id = Guid.NewGuid().CreateDynamicRoute().ToString(); _collectionChildrenIds.Add(item, id); } AddBranch(Route.Create(visitedRoute, id), item, _configuration, visitedObjects); } } else { foreach (var item in args.NewItems.Enumerate()) { if (item == null) { continue; } if (!_collectionChildrenIds.TryGetValue(item, out var id)) { id = Guid.NewGuid().CreateDynamicRoute().ToString(); _collectionChildrenIds.Add(item, id); } AddBranch(Route.Create(visitedRoute, id), item, _configuration, visitedObjects); previousItems?.Add(item); } foreach (var item in args.OldItems.Enumerate()) { if (_collectionChildrenIds.TryGetValue(item, out var id)) { RemoveBranch(Route.Create(visitedRoute, id)); } previousItems?.Remove(item); } } var collectionChangedEventArgs = new ChangedCollectionEventArgs(visitedRoute, sender, args); _configuration.Raise(this, collectionChangedEventArgs); } lock (_collectionChangedRegistry) { var args = new CollectionAttachedEventArgs(visitedRoute, collection); _configuration.Raise(this, args); var record = new Tuple <WeakReference, object[]>(new WeakReference(collection), sourceItems); _collectionChangedRegistry.Add(visitedRoute, record); } _collectionChangedSubscriptions.Subscribe(notifyCollection, NotifyCollectionChangedHandler); } foreach (var item in sourceItems) { if (item == null) { continue; } if (!_collectionChildrenIds.TryGetValue(item, out var id)) { id = Guid.NewGuid().CreateDynamicRoute().ToString(); _collectionChildrenIds.Add(item, id); } AddBranch(Route.Create(visitedRoute, id), item, _configuration, visitedObjects); } } foreach (var reference in source.GetPropertyReferences()) { AddBranch(visitedRoute, reference, configuration, visitedObjects); } }