You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 05:45:31