void ObservableCollectionChanged(object sender, NotifyCollectionChangingEventArgs e) { if (!TrackChanges) { return; } ThrowIfFromDifferentThread(); if (ExcludedTypes.Contains(sender.GetType())) { return; } if (!handlingDisconnected) { //we got an event through the normal path, so we're sure this object is still connected UnsubscribeDisconnected(sender); } NotifyCollectionChangeMemento memento; var key = new CollectionChangeKey(sender, e.Item, e.Action); collectionChangeMementos.TryGetValue(key, out memento); if (memento == null) { throw new NotSupportedException("CollectionChanged received without CollectionChanging"); } memento.RememberNewValues(sender, e); collectionChangeMementos.Remove(key); lastCollectionChangeMemento = memento; // exceptional situation, same object! if (!TypeUtils.IsAggregationList(sender)) { if (ReferenceEquals(memento.OldValue, memento.NewValue)) { UnsubscribeDisconnected(memento.OldValue); } UnsubscribeDisconnected(memento.NewValue); // newly added value } LogCollectionChanged(memento, e); }
protected bool Equals(CollectionChangeKey other) { return sender.Equals(other.sender) && item.Equals(other.item) && action == other.action; }
void ObservableCollectionChanging(object sender, NotifyCollectionChangingEventArgs e) { if (!TrackChanges) { return; } ThrowIfFromDifferentThread(); if (ExcludedTypes.Contains(sender.GetType())) { return; } if (!handlingDisconnected) { //we got an event through the normal path, so we're sure this object is still connected UnsubscribeDisconnected(sender); } // normally this will not happen unless event handlers are subscribed before undo/redo, check nevertheless if (e.Cancel) { log.DebugFormat("CollectionChanging event was cancelled, skipping undo"); return; } FixCancelledCollectionChangingEvents(); var key = new CollectionChangeKey(sender, e.Item, e.Action); if (collectionChangeMementos.ContainsKey(key)) { if (handlingDisconnected) { return; //already received } throw new NotSupportedException("The same CollectionChanging event fired twice (probably two different event paths)"); } if (restoringMemento != null) { if (!IsCollectionChangeEventPartOfRestore(sender, e.Index)) { throw new InvalidOperationException("Side-effect code detected (code which changes object tree in setters or in event handlers) which is not marked with [EditAction]"); } } if (eventCascadeCallLevel > 1 && !handlingDisconnected) { if (eventCascadeCallLevel < expectedEventCascadeCallLevel) { expectedEventCascadeCallLevel = eventCascadeCallLevel; } // we must be in a side-effect if (expectedEventCascadeCallLevel != eventCascadeCallLevel) { log.DebugFormat("CascadeEventLevel: {0}, ExpectedCascadeEventLevel: {1}", eventCascadeCallLevel, expectedEventCascadeCallLevel); throw new InvalidOperationException("Side-effect code detected (code which changes object tree in setters or in event handlers) which is not marked with [EditAction]"); } } var memento = (NotifyCollectionChangeMemento)mementoStack.Peek(); memento.RememberOldValues(sender, e); collectionChangeMementos[key] = memento; if (!handlingDisconnected && !TypeUtils.IsAggregationList(sender)) { if (restoringMemento == null) { HandleIfTransactional(memento.OldValue, true); } SubscribeDisconnected(memento.OldValue); // listen to the changes in disconnected objects } }