本文告诉大家在使用 ObservableCollection 时,抛出 InvalidOperationException 异常,提示 Cannot change ObservableCollection during a CollectionChanged event 内容,的原因和解决方法

准确来说,这个异常和 WPF 是没有任何关系的。这个异常是 ObservableCollection 类型抛出的,而 ObservableCollection 类型是在 dotnet runtime 定义的,放在 System.ObjectModel 里,而且此异常可以在除 WPF 的其他框架,比如控制台或者 UWP 上复现

想要解决此问题,还请先了解一下此异常抛出的原因

在 ObservableCollection 的设计上,是可以了解列表的变更。而在列表的变更了解,是通过 CollectionChanged 事件实现。然而事件的触发,稍微了解 C# 语法的开发者都知道,是每个方法独立执行。这就让 ObservableCollection 存在一个设计上需要解决的问题,那就是如果事件 CollectionChanged 被加等两次,意味着有两次方法的调用。如果在第一次调用方法时,在此方法内再次修改了 ObservableCollection 列表的元素,那么将会让第二个方法进入的时候,所获取的状态和第一个方法所获取的一定不相同

这个设计上的问题,是很难解决的。既然很难解决,那就不解决了,将问题交给开发者好了,在 ObservableCollection 判断如果 CollectionChanged 事件被加等大于 1 次,同时在事件触发的过程中,进行集合的变更,将会抛出 InvalidOperationException 异常,提示 Cannot change ObservableCollection during a CollectionChanged event 内容

这就是从设计上的原因。那为什么只加等 1 次时不抛出呢?那是因为既然只有一次,那改不改都影响不了当前的进入的方法的状态

由于 CollectionChanged 事件加等的次数决定了 InvalidOperationException 是否抛出,从而让一些开发者拿到错误的结论: 在 CollectionChanged 事件里面修改集合本身是安全的。或者反过来,在 CollectionChanged 事件里面修改集合本身是不安全的

正确的行为是: 当 CollectionChanged 事件加等的委托在 1 个以内时,在 CollectionChanged 事件里面修改集合本身是安全的。如果 CollectionChanged 事件加等的委托大于 1 个时,在 CollectionChanged 事件里面修改集合本身是不安全的

从代码上,在 ObservableCollection 的各个更改集合的函数,例如 InsertItem ClearItems RemoveItem 等,都会调用 CheckReentrancy 方法,判断是否存在重入。在 CheckReentrancy 方法的实现如下

        /// <summary> Check and assert for reentrant attempts to change this collection. </summary>
/// <exception cref="InvalidOperationException"> raised when changing the collection
/// while another collection change is still being notified to other listeners </exception>
protected void CheckReentrancy()
{
if (_blockReentrancyCount > 0)
{
// we can allow changes if there's only one listener - the problem
// only arises if reentrant changes make the original event args
// invalid for later listeners. This keeps existing code working
// (e.g. Selector.SelectedItems).
if (CollectionChanged?.GetInvocationList().Length > 1)
throw new InvalidOperationException(SR.ObservableCollectionReentrancyNotAllowed);
}
}

上面代码的 _blockReentrancyCount 是在 OnCollectionChanged 方法和 BlockReentrancy 方法使用的。在没有重写 ObservableCollection 的情况下,可以认为 _blockReentrancyCount 只有在 OnCollectionChanged 方法更改

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler? handler = CollectionChanged;
if (handler != null)
{
// Not calling BlockReentrancy() here to avoid the SimpleMonitor allocation.
_blockReentrancyCount++;
try
{
handler(this, e);
}
finally
{
_blockReentrancyCount--;
}
}
}

也就是说,在 CollectionChanged 事件触发进入的方法里面,一定是判断 if (_blockReentrancyCount > 0) 通过的。也就说接下来只需要看 if (CollectionChanged?.GetInvocationList().Length > 1) 判断即可。这里的 GetInvocationList 是 CollectionChanged 事件对应的委托的数量,只要超过 1 个就炸

了解了原因,那么解决方法也很简单。要么是在 CollectionChanged 事件里面修改集合时确保只让 CollectionChanged 加等一个委托。要么就是继承 ObservableCollection 类型,重写 OnCollectionChanged 方法,不要修改 _blockReentrancyCount 字段。要么就是等待 CollectionChanged 事件触发完成之后,通过 Dispatcher 的 InvokeAsync 方法调度出去执行

WPF 解决 ObservableCollection 提示 Cannot change ObservableCollection during a CollectionChanged event 异常的更多相关文章

  1. WPF编译时提示“...不包含适合于入口点的静态‘Main’方法 ...”

    今天看了一下wpf的Application类方面的知识,一个windows应用程序由一个Application类的实例表示,该类跟踪在应用程序中打开的所有窗口,决定何时关闭应用程序(属性 Shutdo ...

  2. UWP ObservableCollection<Evaluate>集合中ObservableCollection<PictureInfo>变更通知到xaml界面

    ObservableCollection<Evaluate> EvaluateList = new ObservableCollection<Evaluate>();//评论集 ...

  3. WPF解决界面全屏化但不遮挡任务栏的问题

    原文:WPF解决界面全屏化但不遮挡任务栏的问题 学习C#有一段时间了,现在跟着做项目,碰到有个客户端界面总是全屏,对于客户来说没有任务栏很不习惯,所以做了些略微的修改   </pre>&l ...

  4. WPF解决按钮上被透明控件遮盖时无法点击问题

    原文:WPF解决按钮上被透明控件遮盖时无法点击问题 IsHitTestVisible="False" 在控件上设置如上属性即可,即可让透明控件不触发点击效果

  5. 解决maven项目Cannot change version of project facet Dynamic web module to 3.0/3.1

    解决maven项目Cannot change version of project facet Dynamic web module to 3.0 1.打开项目所在目录下的.settings文件夹 打 ...

  6. WPF编译时提示"xxx不包含适合于入口点的静态 Main方法xxx"

    WPF编译时提示"xxx不包含适合于入口点的静态 Main方法xxx"生成的时候一直报"xxx不包含适合于入口点的静态 Main 方法xxx" 看到这个问题首先 ...

  7. 解决PowerDesigner提示This data item is already used in a primary identifier

    解决PowerDesigner提示This data item is already used in a primary identifier 解决PowerDesigner提示This data i ...

  8. WPF 解决无边框产生的相关问题

    原文:WPF 解决无边框产生的相关问题 最大化: 在设置WindowStyle="None" AllowsTransparency="True"后,最大化会覆盖 ...

  9. 【WPF】WPF中的List<T>和ObservableCollection<T>

    在WPF中 控件绑定数据源时,数据源建议采用 ObservableCollection<T>集合 ObservableCollection<T> 类:表示一个动态数据集合,在添 ...

  10. 【转】关于编写WPF UserControl时提示The name 'InitializeComponent' does not exist in the current contextr的解决!

    1.打开.csproj(工程)文件. 2.找到<Import Project="$(MSBuildBinPath)/Microsoft.CSharp.targets" /&g ...

随机推荐

  1. vue-router动态注册

    来源 写路由时每新建一个路由都需要import一下或其他方式(如箭头函数import)很是麻烦,有麻烦就有需求,于是以下这篇文章就来了 吹水 要想动态注册路由,那么就需要制定规则,即每个路由有一定的规 ...

  2. 在ubuntu安装QT

    在ubuntu安装 安装motrix motrix下载 下载对应版本的QT QT下载 授权run文件 sudo chmod +x xxx.run 运行run文件 ./ xxx.run 运行界面 安装完 ...

  3. MySQL创建和操纵表

    表创建基础 CREATE TABLE customers ( cust_id int NOT NULL AUTO_INCREMENT , cust_name char(50) NOT NULL , c ...

  4. #dp#洛谷 4399 [JSOI2008]Blue Mary的职员分配

    题目 分析 设\(dp[i][day][j][k]\)表示当前雇员个数为\(i\), 距离上次发广告时间为\(day\),获得的金钱和声望分别为\(j,k\) 注意\(day\)是\([0\sim 3 ...

  5. nginx集成brotli压缩算法

    本文于2017年2月中旬完成,发布在个人博客网站上. 考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来. Google开源Brotli压缩算法 Brotli是一种全新的数据 ...

  6. OpenAtom OpenHarmony三方库创建发布及安全隐私检测

    OpenAtom OpenHarmony三方库(以下简称"三方库"或"包"),是经过验证可在OpenHarmony系统上可重复使用的软件组件,可帮助开发者快速开 ...

  7. Java 异常处理与正则表达式详解,实例演练及最佳实践

    Java 异常 - Try...Catch 在 Java 代码执行期间,可能会发生各种错误,包括程序员编码错误.用户输入错误以及其他不可预料的状况. 当错误发生时,Java 通常会停止并生成错误消息, ...

  8. Docker学习路线2:底层技术

    了解驱动Docker的核心技术将让您更深入地了解Docker的工作原理,并有助于您更有效地使用该平台. Linux容器(LXC) Linux容器(LXC)是Docker的基础. LXC是一种轻量级的虚 ...

  9. C# \n与\\n区别

    \n是换行符:\\n第一个\是转义字符,也就是说,\\n在屏幕上显示\n

  10. 《深入理解Java虚拟机》读书笔记:内存分配策略

    Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存.关于回收内存这一点,我们已经使用了大量篇幅去介绍虚拟机中的垃圾收集器体系以及运作原理 ...