在这里插入图片描述

前言

欢迎关注【dotnet研习社】,今天我们聊聊一个基础问题“集合已修改:可能无法执行枚举操作”背后的设计。

在日常 C# 开发中,我们常常会操作集合(如 List<T>Dictionary<K,V> 等)。一个新手开发者极有可能遇到下面这个经典异常:

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

这通常意味着你在 遍历集合的过程中尝试修改集合本身(添加或删除元素),这是被禁止的。本文将深入剖析这个问题产生的原因,并分享常见的几种 安全解决方案,帮助我们从容应对这一异常。
在这里插入图片描述

一、问题复现

来看一个简单的例子:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };foreach (int num in numbers)
{if (num % 2 == 0){numbers.Remove(num); // 报错!}
}

运行后会抛出异常:

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

这是因为 foreach 在枚举集合时,会维护一个内部状态来防止在枚举过程中破坏结构,一旦结构变动,就会抛出异常。

二、常见的正确做法

方法 1:倒序 for 循环移除元素

适用于 List<T> 这种支持索引的集合:

for (int i = numbers.Count - 1; i >= 0; i--)
{if (numbers[i] % 2 == 0){numbers.RemoveAt(i);}
}

✅ 倒序循环可以避免因为索引变动导致的跳过元素或崩溃。

方法 2:使用 LINQ 的 Where + ToList() 创建副本遍历

foreach (var num in numbers.Where(n => n % 2 == 0).ToList())
{numbers.Remove(num);
}

ToList() 会创建一个集合副本,这样你就可以安全地对原集合进行修改了。

方法 3:临时列表收集要移除的项,二次遍历移除

var toRemove = new List<int>();
foreach (var num in numbers)
{if (num % 2 == 0){toRemove.Add(num);}
}
foreach (var num in toRemove)
{numbers.Remove(num);
}

✅ 这种方法安全可靠,尤其适合处理复杂条件删除场景。

方法 4:直接使用 List<T>.RemoveAll()

这是最简洁的一种方式:

numbers.RemoveAll(n => n % 2 == 0);

✅ 适用于只需要从集合中删除符合某个条件的元素场景。

三、适用于不同集合类型的说明

集合类型遍历时可修改?推荐处理方式
List<T>倒序/临时列表/RemoveAll
Dictionary<K,V>ToList()拷贝键值对后操作
HashSet<T>先收集,后统一移除
ConcurrentBag<T>支持并发读写,无需额外处理

如果正在开发多线程程序,强烈推荐使用线程安全集合,如 ConcurrentDictionary<K,V>ConcurrentQueue<T> 等。

四、深入理解为何不能修改

  • foreach 的底层是使用了 IEnumerator
  • 当修改集合时(比如 Remove()),集合的 version 字段会更新;
  • IEnumerator 检测到版本变动后,会抛出 InvalidOperationException,以防止出现难以调试的数据错误。

我们可以通过查看 .NET 源码中关于 List<T>IEnumerator 以及 version 字段的真实实现,验证上面的描述并深入理解:

  • https://github.com/dotnet/runtime
  • 关键路径 src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs

在该文件中可以看到 List<T> 的实现细节:

示例:List<T>.Enumerator.MoveNext() 中的 version 检查

public bool MoveNext()
{List<T> localList = list;if (version == localList._version && (index < localList._size)){current = localList._items[index++];return true;}return MoveNextRare();
}

而在 MoveNextRare() 中可以看到抛出异常的逻辑:

private bool MoveNextRare()
{if (version != list._version){ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();}index = list._size + 1;current = default!;return false;
}

说明只要外部在枚举过程中修改了集合(导致 _version 改变),枚举器就会感知并抛出异常。
这种机制的目的是保护开发者避免数据一致性错误,虽然它带来了限制,但也增强了代码的健壮性。

五、总结一句话

遍历集合时不要修改集合本身。

如果需要修改,请先复制副本延后批量处理,不要在 foreach 中直接 AddRemove

六、附加:通用工具方法(删除满足条件的元素)

我们可以封装一个更通用的方法,供多处复用:

public static void SafeRemove<T>(List<T> list, Func<T, bool> predicate)
{list.RemoveAll(predicate);
}

使用方式:

SafeRemove(numbers, n => n % 2 == 0);

七、延伸阅读推荐

  • .NET 源码解析:List 是如何防止你在遍历中修改它的?
  • .NET GitHub 源码结构导览
  • Stack Overflow 高票回答:Why does modifying a list while iterating cause an exception?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93403.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93403.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93403.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【工具】图床完全指南:从选择到搭建的全方位解决方案

前言 在数字化内容创作的时代&#xff0c;图片已经成为博客、文档、社交媒体等平台不可或缺的元素。然而&#xff0c;如何高效、稳定地存储和分发图片资源&#xff0c;一直是内容创作者面临的重要问题。图床&#xff08;Image Hosting&#xff09;作为专门的图片存储和分发服务…

深度学习篇---PaddleDetection模型选择

PaddleDetection 是百度飞桨推出的目标检测开发套件&#xff0c;提供了丰富的模型库和工具链&#xff0c;覆盖从轻量级移动端到高性能服务器的全场景需求。以下是核心模型分类、适用场景及大小选择建议&#xff08;通俗易懂版&#xff09;&#xff1a;一、主流模型分类及适用场…

cmseasy靶机密码爆破通关教程

靶场安装1.首先我们需要下载一个cms靶场CmsEasy_7.6.3.2_UTF-8_20200422,下载后解压在phpstudy_pro的网站根目录下。2.然后我们去访问一下安装好的网站&#xff0c;然后注册和链接数据库3.不知道自己数据库密码的可以去小皮面板里面查看4.安装好后就可以了来到后台就可以了。练…

【C语言】指针深度剖析(一)

文章目录一、内存和地址1.1 内存的基本概念1.2 编址的原理二、指针变量和地址2.1 取地址操作符&#xff08;&&#xff09;2.2 指针变量和解引用操作符&#xff08;*&#xff09;2.2.1 指针变量2.2.2 指针类型的解读2.2.3 解引用操作符2.3 指针变量的大小三、指针变量类型的…

半导体企业选用的跨网文件交换系统到底应该具备什么功能?

在半导体行业的数字化转型过程中&#xff0c;跨网文件交换已成为连接研发、生产、供应链的关键纽带。半导体企业的跨网文件交换不仅涉及设计图纸、工艺参数等核心知识产权&#xff0c;还需要满足跨国协同、合规审计等复杂需求。那么&#xff0c;一款适合半导体行业的跨网文件交…

影刀RPA_初级课程_玩转影刀自动化_网页操作自动化

声明&#xff1a;相关内容来自影刀学院&#xff0c;本文章为自用笔记&#xff0c;切勿商用&#xff01;&#xff08;若有侵权&#xff0c;请联络删除&#xff09; 1. 基本概念与操作 1.1 正确处理下拉框元素&#xff08;先判断页面元素&#xff0c;后进行流程编制&#xff09;…

Spark初探:揭秘速度优势与生态融合实践

更多推荐阅读 Spark与Flink深度对比&#xff1a;大数据流批一体框架的技术选型指南-CSDN博客 LightProxy使用操作手册-CSDN博客 Sentry一看就会教程_sentry教程-CSDN博客 微前端架构解析&#xff1a;核心概念与主流方案特性对比_微前端方案对比-CSDN博客 目录 Spark为何比Hadoo…

详谈OSI七层模型和TCP/IP四层模型以及tcp与udp为什么是4层,http与https为什么是7层

一、网络模型&#xff1a;OSI七层 vs TCP/IP四层OSI七层模型 (理论参考模型):目的&#xff1a;提供一个标准化的理论框架&#xff0c;用于理解网络通信过程和各层的功能划分&#xff0c;促进不同厂商设备的互操作性。它是一个理想化的模型。分层 (从下到上):物理层&#xff1a;…

ClickHouse 高性能实时分析数据库-索引与数据跳过(查询的“瞬移”能力)

告别等待&#xff0c;秒级响应&#xff01;这不只是教程&#xff0c;这是你驾驭PB级数据的超能力&#xff01;我的ClickHouse视频课&#xff0c;凝练十年实战精华&#xff0c;从入门到精通&#xff0c;从单机到集群。点开它&#xff0c;让数据处理速度快到飞起&#xff0c;让你…

Jetpack - Room(Room 引入、Room 优化)

一、Room 引入 1、基本介绍 Room 在 SQLite 上提供了一个抽象层&#xff0c;以便在充分利用 SQLite 的强大功能的同时&#xff0c;能够流畅地访问数据库&#xff0c;官方强烈建议使用 Room 而不是 SQLite 2、演示 &#xff08;1&#xff09;Setting 模块级 build.gradle depend…

【江科大CAN】2.1 STM32 CAN外设(上)

2.1 STM32 CAN外设&#xff08;上&#xff09;2.1.1 STM32 CAN外设简介2.1.2 外围电路设计2.1.3 STM32 CAN内部结构2.1.4 发送流程详解2.1.5 接收流程详解2.1.6 关键配置位总结STM32 CAN外设讲解 大家好&#xff0c;欢迎继续观看CAN总线入门教程。本节开始&#xff0c;我们正式…

人工智能技术革命:AI工具与大模型如何重塑开发者工作模式与行业格局

引言&#xff1a;AI技术爆发的时代背景过去五年间&#xff0c;人工智能领域经历了前所未有的爆发式增长。从2020年GPT-3的横空出世到2023年多模态大模型的全面突破&#xff0c;AI技术已经从实验室走向了产业应用的前沿。开发者作为技术生态的核心推动者&#xff0c;其工作模式正…

傅里叶变换

傅里叶变换:运用频域的出发点就是能够将波形从时域变换到频域&#xff0c;用傅里叶变换可以做到这一点。有如下3种傅里叶变换类型&#xff1a;1.傅里叶积分(FI); 2.离散傅里叶变换(DFT); 3.快速傅里叶变换(FFT)。傅里叶积分是一种将时域的理想数学表达变换成频域描述的数学技术…

【IQA技术专题】纹理相似度图像评价指标DISTS

纹理一致性图像评价指标: Image Quality Assessment: Unifying Structure and Texture Similarity&#xff08;2020 PAMI&#xff09;专题介绍一、研究背景二、方法总览2.1 初始变换2.2 纹理表示和结构表示2.3 DISTS指标2.4 优化DISTS指标三、实验结果四、总结本文将对统一图像…

windows下Docker安装路径、存储路径修改

一、命令行指定安装路径​ ​​下载安装包​​&#xff1a;从Docker官网获取安装程序&#xff08;如Docker Desktop Installer.exe&#xff09;。​​运行PowerShell​​&#xff1a; & "H:\Docker Desktop Installer.exe" install --installation-dir"F:…

thingsboard 自定义动作JS编程

在 ThingsBoard 中实现 自定义动作&#xff08;Custom Action&#xff09;的 JavaScript 编程&#xff0c;主要通过“Custom action (with HTML template&#xff09;”方式完成&#xff0c;适用于创建弹窗、编辑实体、控制设备等交互行为。 实现步骤&#xff08;以添加设备或资…

Spring Boot 简单接口角色授权检查实现

一、背景与目标在Spring Boot应用开发中&#xff0c;接口级别的权限控制是系统安全的重要组成部分。本文将介绍一种简单直接的接口角色授权检查实现方案&#xff0c;适合快速开发和安全合规检查场景。二、技术方案概述本方案采用自定义注解拦截器的方式实现&#xff0c;具有以下…

PytorchLightning最佳实践日志篇

在 PyTorch Lightning&#xff08;PL&#xff09;中&#xff0c;日志系统是 “炼丹” 过程中复现实验、对比效果、排查问题的核心工具。结合实际工程经验&#xff0c;总结以下最佳实践和技巧&#xff0c;帮助提升实验效率&#xff1a; 一、日志工具的选择与配置 PL 通过统一的s…

基于JavaWeb的兼职发布平台的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.6系统展示系统首页用户登录招聘信…

Linux学习--C语言(指针3)

1.指针函数和函数指针1.1 指针函数指针函数是函数&#xff0c;函数的返回值是指针不能返回局部变量的地址指针函数返回的地址可以作为下一个函数调用的参数1.2 函数指针函数指针是指针&#xff0c;指针指向一个函数#include <stdio.h>int Add(int x, int y) {return x y…