如何实现ObservableCollection元素自动包装?解决修改运行时异常
问题
尝试实现ObservableCollection元素的自动包装功能,通过CollectionChanged事件检查元素是否为WrapperUserControl类型,若不是则创建容器对象,将原元素移入其中并替换集合中的当前元素,但运行时报错——无法在CollectionChanged事件中修改ObservableCollection。请问该如何正确实现?
原示例代码:
[ContentProperty("Items")] public partial class BaseUserControl : UserControl { public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<FrameworkElement>), typeof(BaseUserControl)); public ObservableCollection<FrameworkElement> Items { get { return (ObservableCollection<FrameworkElement>)GetValue(ItemsProperty); } set { SetValue(ItemsProperty, value); } } public BaseUserControl() { InitializeComponent(); Items.CollectionChanged += Items_CollectionChanged; } private void Items_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { for (int i = 0; i < Items.Count; i++) { if (Items[i] is not WrapperUserControl) { WrapperUserControl wrapper = new WrapperUserControl(); wrapper.Items.Add(Items[i]); Items[i] = wrapper; return; } } // do something else } }
解决方案
报错原因是:ObservableCollection触发CollectionChanged事件时,正处于内部变更通知的流程中,此时直接修改集合会打破内部状态一致性,引发异常。以下两种方法可以解决这个问题:
方法一:用Dispatcher异步延迟修改
通过Dispatcher将修改逻辑放到当前集合变更通知完成后执行,避开冲突:
private void Items_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { // 仅处理新增元素的场景,避免无意义的重复检查 if (e?.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Add) return; // 异步执行修改,避开当前集合变更流程 Dispatcher.BeginInvoke(() => { for (int i = 0; i < Items.Count; i++) { if (Items[i] is not WrapperUserControl) { var originalElement = Items[i]; var wrapper = new WrapperUserControl(); wrapper.Items.Add(originalElement); Items[i] = wrapper; break; // 处理一个元素后跳出,避免一次触发多次变更通知 } } }); }
方法二:自定义ObservableCollection子类(推荐)
从根源上处理元素的自动包装,重写集合的插入、替换方法,在元素进入集合前完成包装,彻底避免事件中修改集合的问题:
// 自定义自动包装的集合类 public class WrapperObservableCollection : ObservableCollection<FrameworkElement> { protected override void InsertItem(int index, FrameworkElement item) { // 自动包装非WrapperUserControl类型的元素 var wrappedItem = item as WrapperUserControl ?? new WrapperUserControl(); if (!(item is WrapperUserControl)) { wrappedItem.Items.Add(item); } base.InsertItem(index, wrappedItem); } protected override void SetItem(int index, FrameworkElement item) { var wrappedItem = item as WrapperUserControl ?? new WrapperUserControl(); if (!(item is WrapperUserControl)) { wrappedItem.Items.Add(item); } base.SetItem(index, wrappedItem); } }
然后修改BaseUserControl中的依赖属性定义:
[ContentProperty("Items")] public partial class BaseUserControl : UserControl { public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(WrapperObservableCollection), typeof(BaseUserControl), new PropertyMetadata(new WrapperObservableCollection())); public WrapperObservableCollection Items { get { return (WrapperObservableCollection)GetValue(ItemsProperty); } set { SetValue(ItemsProperty, value); } } public BaseUserControl() { InitializeComponent(); // 无需再订阅CollectionChanged事件,子类已处理自动包装 } }
这种方法逻辑更清晰,也不会出现集合变更时的冲突问题,是更优的实现方式。
内容的提问来源于stack exchange,提问作者Notrum666
相关产品推荐
相关产品推荐

