C#開發基礎之深入理解“集合遍歷時不可修改”的異常背后的設計

在這里插入圖片描述

前言

歡迎關注【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…