本文告诉大家在使用 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. 三维模型3DTILE格式轻量化压缩主要技术方法浅析

    三维模型3DTILE格式轻量化压缩主要技术方法浅析 三维模型3DTILE格式轻量化压缩主要技术方法浅析 随着三维地理空间数据的应用日益广泛,为了更快速地传输和存储这些大规模数据,3DTile格式的轻量 ...

  2. 鸿蒙HarmonyOS实战-ArkUI组件(Row/Column)

    前言 HarmonyOS的布局组件是一组用于构建用户界面布局的组件,包括线性布局.相对布局.网格布局等.这些组件帮助开发者以简单和灵活的方式管理和组织应用程序中的视图,并支持多种不同的设备屏幕尺寸和方 ...

  3. 使用Go语言开发一个短链接服务:六、链接跳转

    章节  使用Go语言开发一个短链接服务:一.基本原理  使用Go语言开发一个短链接服务:二.架构设计  使用Go语言开发一个短链接服务:三.项目目录结构设计  使用Go语言开发一个短链接服务:四.生成 ...

  4. Python列表list 分片实例

    1 a = list(range(10)) 2 print(a[::]) #复制一个列表 3 print(a[::2]) #每隔2个取一次 4 print(a[::3]) #每隔3个取一次 5 6 p ...

  5. #CDQ分治,单调栈,双指针#BZOJ 4237 稻草人 AT1225 かかし

    洛谷传送门 BZOJ 4237 稻草人 题意 在一个平面直角坐标系上给出\(n\)个点, 问有多少个点对\((i,j)\)满足\(x_i<x_j,y_i<y_j\), 而且对于\(n\)个 ...

  6. #NTT,DP#U138580 简单的打击

    题目 给出两个等长的序列\(a,b\), 重排序列\(b\),使得\(a+b\)众数出现的次数最多 分析 设\(f[i]\)表示众数为\(i\)的贡献,那么 \(f[i]=\sum_{j<i}m ...

  7. JDK11的新特性:新的HTTP API

    目录 简介 使用HTTP Client请求的基本流程 创建HTTP Client 创建HTTP Request 发送HTTP请求 异步HTTP请求 总结 JDK11的新特性:新的HTTP API 简介 ...

  8. 共筑使能千行百业的数字底座 | HDC 2022松湖对话顺利召开

     11月5日,华为开发者大会2022松湖对话在东莞松山湖凯悦酒店召开,开放原子开源基金会秘书长冯冠霖.华为终端BG软件部总裁龚体.深圳国家金融科技测评中心董事长钟剑.鸿湖万联(江苏)科技发展有限公司董 ...

  9. std::thread 五:打包任务(packaged_task)

    #include <iostream> #include <thread> #include <mutex> #include <list> #incl ...

  10. 教你构建一个优秀的SD Prompt

    构建一个优秀的Prompt 在使用Stable Diffusion AI时,构建一个有效的提示(Prompt)是至关重要的第一步.这个过程涉及到创造性的尝试和对AI行为的理解.这里我会对如何构建一个好 ...