Wednesday, September 26, 2012

Why does Unsubscribing DataContextChanged Causes InvalidOperation Exception due to Collection Modified

Why does Unsubscribing DataContextChanged Causes InvalidOperation Exception due to Collection Modified

I recently stumbled across an issue in silverlight with using the datacontext changed event.

If you subscribe to a changed event and then immediately unsubscribe it will throw an exception,

DataContextChanged += MainPage_DataContextChanged; void MainPage_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {   var vm = e.NewValue as VM;   if(vm != null)   {      DataContextChange-= MainPage_DataContextChanged;//throws invalidoperationexception for collection modified   } } 

to fix this I just unsubscribe the event later, in this situation the requirement is to unsubscribe sooner rather than later so this works.

DataContextChanged += MainPage_DataContextChanged; void MainPage_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {   var vm = e.NewValue as VM;   if(vm != null)   {       //forces item onto the dispatcher queue so anything needing to happen with 'collections' happens first       Dispatcher.BeginInvoke(()=>         {      DataContextChange-= MainPage_DataContextChanged;//throws invalidoperationexception for collection modified          });   } } 

I'm guessing the collections are the Child elements of all the different controls in the visual tree, and I'm guessing their updates are probably happening on the dispatcher queue so my question is this:

Why does the event being unsubscribed after it has fired affect collections that are going to be modified or updated after this?

EDIT: After giving this some thought Could this have anything to do with the event handlers invocation list being modified before its finished?

Answers & Comments...

Answer: 1

Your suspicions about the invocation list being modified are correct.

Here is the code that fires the DataContextChanged event, according to dotPeek's decompilation:

private void RaisePublicDataContextChanged() {   if (this._dataContextChangedInfo == null)     return;   object oldValue = this._dataContextChangedInfo.OldValue;   object dataContext = this.DataContext;   if (oldValue == dataContext)     return;   this._dataContextChangedInfo.OldValue = dataContext;   List<DependencyPropertyChangedEventHandler>.Enumerator enumerator = this._dataContextChangedInfo.ChangedHandlers.GetEnumerator();   try   {     // ISSUE: explicit reference operation     while (((List<DependencyPropertyChangedEventHandler>.Enumerator) @enumerator).MoveNext())     {       // ISSUE: explicit reference operation       ((List<DependencyPropertyChangedEventHandler>.Enumerator) @enumerator).get_Current()((object) this, new DependencyPropertyChangedEventArgs(FrameworkElement.DataContextProperty, oldValue, dataContext));     }   }   finally   {     enumerator.Dispose();   } } 

As you can see, the code is using an enumerator to iterate through the collection of handlers. So when you unsubscribe from the event during the invocation of a handler, you are invalidating the enumerator, causing the exception you are seeing.

by : Marty Dillhttp://stackoverflow.com/users/184630




No comments:

Post a Comment

Send us your comment related to the topic mentioned on the blog