为什么取消订阅DataContextChanged会导致InvalidOperation异常,因为Collection Modified

时间:2012-09-26 14:59:27

标签: c# silverlight

我最近在使用datacontext更改事件的Silverlight中遇到了一个问题。

如果您订阅已更改的事件,然后立即取消订阅,则会抛出异常,

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
  }
}

要解决这个问题,我只是稍后取消订阅该事件,在这种情况下,要求是尽早取消订阅,以便这样做。

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
         });
  }
}

我猜测集合是可视化树中所有不同控件的子元素,我猜他们的更新可能发生在调度程序队列上,所以我的问题是:

为什么事件在触发后取消订阅会影响将在此之后修改或更新的集合?

编辑: 在给出一些想法后,这可能与事件处理程序调用列表在完成之前被修改有关吗?

1 个答案:

答案 0 :(得分:3)

您对正在修改的调用列表的怀疑是正确的。

根据dotPeek的反编译,以下是触发DataContextChanged事件的代码:

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();
  }
}

如您所见,代码使用枚举器来遍历处理程序集合。因此,当您在调用处理程序期间取消订阅该事件时,您将使枚举数无效,从而导致您看到的异常。