This article mainly introduces: collection and index exceptions, mainly including these types: IndexOutOfRangeException, KeyNotFoundException, ArrayTypeMismatchException, RankException, InvalidOperationException.
〇.Foreword
The previous article has listed all the common abnormal types. This article continues to introduce the abnormal characteristics of each category.
This article mainly introduces:Collection and index exceptionsmainly including these types:IndexOutOfRangeException、KeyNotFoundException、ArrayTypeMismatchException、RankException、InvalidOperationException。
Click the link to view this series of articles:C# Exception detailed explanation series.
1. Overview of collection and index exceptions
| abnormal | Who threw it | One sentence reason | Preferred alternative |
|---|---|---|---|
IndexOutOfRangeException |
array, string,Span、Memorymultidimensional array |
Subscript <0 or ≥ Length | judge Length/Count、^ and scope,ElementAtOrDefault |
KeyNotFoundException |
Dictionary/ConcurrentDictionary/SortedDictionary of [key] |
key does not exist | TryGetValue、GetValueOrDefault(.NET 8+)、TryAdd |
ArrayTypeMismatchException |
When writing a covariant array | The actual number assembly at runtime cannot fit into this type. | Don’t use covariant arrays, use List/IReadOnlyList |
RankException |
Array.Copy/CopyTo/ConstrainedCopy wait |
The source and target dimensions (Rank) are different | judge Rank、GetLength(d)distinguish between jagged arrays and multidimensional arrays |
InvalidOperationException |
Enumerator version verification, empty set element retrieval, etc. | The collection was modified/the object is in an error state | Reverse order traversal, snapshot,RemoveAllconcurrent collection |
These 5 anomalies have one thing in common:They almost all mean “the program logic is wrong” rather than “the user input is bad”. Therefore the general strategy is “use checks or safer APIs to eliminate exceptions before they occur” rather than in the outer layer catch. The only exception is InvalidOperationExceptionhalf of its scenarios are state machine/concurrency issues, which need to be avoided by design.
core principles: Anything that can be judged by length,Try* If the API, immutable snapshots or type system (generic immutability) block it at compile time/call time, don’t leave it to the runtime.
Among these five anomalies, except InvalidOperationException Half of the scenarios need to be solved by concurrent design, and the remaining four can achieve “zero online occurrence”.

2. Introduction and simple examples of each exception
2.1 IndexOutOfRangeException – out-of-bounds access
Trigger condition: subscript < 0 or ≥ Length。
Note that it is an exception to the “array/contiguous memory” semantics:string[i]、T[]、Spanmulti-dimensional arrays will throw it; and List Throwing is ArgumentOutOfRangeException——This is the easiest thing to remember when interviewing and troubleshooting.
// ① 切分后想当然地取下标(外部数据最常见)
var parts = "a,b".Split(',');
var third = parts[2]; // 💥 IndexOutOfRangeException
// ② 差一错误(<=)
int[] a = new int[5];
for (int i = 0; i <= a.Length; i++) // 💥 i == 5 时
a[i] = i;
// ③ C# 8 的 ^ 运算符:^0 本身就是非法的
var last = a[^0]; // 💥 永远是越界,编译不报错
// ④ 多维数组按“扁平思维”访问
int[,] m = new int[3, 4];
var v = m[0, 12]; // 💥 每维独立校验
// ✅ 正确姿势
if (parts.Length > 2) third = parts[2]; // 明确长度判断
third = parts.ElementAtOrDefault(2) ?? ""; // LINQ,性能略低但安全
var last = a[^1]; // ^1 才是最后一个
// Span/Memory 场景用 TrySlice / Slice 前先判 Length
if (span.Length >= 3) var chunk = span.Slice(0, 3);
Special attention:
- performance level: JIT will “promote” the bounds check inside the loop to outside the loop (bounds check elision), soManually add
if (i < len)It does not slow down, but often helps JIT eliminate checks.;butDon’t useunsafePointers to bypass checks in exchange for performancecrossing the boundary will cause memory corruption, which is difficult to reproduce. - Empty set collection
[0]It is one of the most frequent sources of transgression online, especiallyFirstOrDefault()There is no default value after that,Split/The regular matching result is empty and the JSON array is empty. - Multiple threads share a variable index (shared
int index++) will occasionally cross the boundary. This type of bug only appears during stress testing.
2.2 KeyNotFoundException – dictionary key does not exist
// 异常触发示例
var cfg = new Dictionary { ["Host"] = "localhost" };
var port = cfg["Port"]; // 💥 The given key 'Port' was not present in the dictionary.
// ✅ 单次查找:TryGetValue(避免 ContainsKey + 索引器 的两次哈希)
if (cfg.TryGetValue("Port", out var port)) { ... }
// ✅ .NET 6+:直接给默认值,语义最干净
var port = cfg.GetValueOrDefault("Port", "5432");
// ✅ 需要“没有就插入”时,避免两次查找(.NET 7+)
ref var val = ref CollectionsMarshal.GetValueRefOrAddDefault(cfg, "Port", out bool exists);
val = "5432"; // ref 直接写回,零额外分配、零二次哈希
// ✅ 并发场景
var cd = new ConcurrentDictionary();
cd.GetOrAdd("k", _ => Compute()); // 不要 cd["k"] ?? cd["k"] = ...
Special attention:
- key equivalence: Custom type keys must be rewritten at the same time
GetHashCodeandEquals(record/record structhas been synthesized); useMutable objects or arrays as keysIt is a classic trap – the object is modified after being entered into the dictionary, the hash bucket does not match, and the key is “lost”, which appears to be intermittent.KeyNotFoundException。 - Capitalization and culture: String keys are case-sensitive by default and use sequential comparison; configuration/protocol parsing suggestions
StringComparer.OrdinalIgnoreCase。 - concurrent:
Dictionary<,>Not thread-safe, multi-threaded reading and writing may not only throwKeyNotFoundExceptionthere may also be an internal infinite loop/data corruption. More reading and less writing are availableConcurrentDictionaryOr read-write lock. - value type key(such as tuples, structs) will cause boxing and large memory overhead; consider high-frequency scenarios
Span-based search orImmutableDictionary。 - When deserializing or configuring missing fields, don’t rely on exceptions to determine whether a field exists – use
TryGetValueOnly then can we distinguish between “missing configuration” and “wrong program”.
2.3 ArrayTypeMismatchException – writing trap of covariant array
Arrays of reference types allow covariance (string[] → object[]), but the runtime object is still an array of subtypes, soType verification is required for every write. If it fails, an exception will be thrown at the line written.。An error is never reported at the assignment point, and the error is postponed to runtime.
object[] arr = new string[1];
arr[0] = "hi"; // ✅
arr[0] = 42; // 💥 ArrayTypeMismatchException
arr[0] = null; // ✅ null 永远兼容
// object[] b = new int[3]; // ❌ 值类型不参与协变,编译期就拦住
| # | scene | illustrate |
|---|---|---|
| 1 | params object[] |
When the actual parameter happens to be a covariant array, it is transparently transmitted at zero cost and without cladding.Log("api", tags) inside tags Still String[]it will explode when written in the method.(object[])tags Just reference conversion cannot save it;(object)tags Only cladding, but the semantics become “an element” |
| 2 | Array.SetValue /reflection |
The parameters are objectthe compiler has no information, and everything is postponed to the runtime storage point. |
| 3 | Array.Copy |
When the dimensions are the same but an element cannot be assigned, element-by-element verification may occur. It may take half of the copying before exploding, indicating that the target array has been contaminated. Use “all or nothing” Array.ConstrainedCopy |
| 4 | Generic handle T[] promoted to object[]/Array to write |
where T : class hour object[] o = arr; Compile passed;dynamic d = arr; d[0]=42; Bypass compilation checks completely |
| 5 | IList Covariant writing |
IList 💥 |
| 6 | Unsafe.As / MemoryMarshal.Cast |
Even the runtime verification is bypassed – no exception is thrown, and the heap is directly damaged. This is a counterexample to the illusion that covariance is at most just throwing exceptions. |
cost:
- Type safety: Errors are pushed from compile time to runtime, and the stack is far away from the root cause.
- Performance: Insert type verification for each write;
Array.CopyWhen the element types are inconsistent, it degenerates into element-by-element copying and losesmemmoveFast path. - API: The same method passes literals normally, but passes an existing array and it explodes, forming a “Heisenberg-style” interface.
// 【如何避免?】
// A. 对外只用只读视图,物理上写不进去
void Print(IReadOnlyList items) { }
void Process(ReadOnlySpan data) { }
// B. params 方法体视为只读;要副本就造真 Object[]
object[] snap = new object[args.Length];
Array.Copy(args, snap, args.Length); // ⚠️ (object[])args.Clone() 救不了,仍是 String[]
// C. 泛型写回保持 T[] 本体
static void Fill(T[] arr, T val) => Array.Fill(arr, val);
// D. 异构容器别用数组:元组/record、List
Several common troubleshooting points:
- criterion: An array or array of “read base class, write hope subclass” appears
IListis a reserve; if replaced by an immutable generic collection, the problem disappears on the spot. - Three-piece diary set:
array.GetType().Name(real runtime type),GetElementType()value to be writtenGetType()。 - Check “Break when thrown” in VS exception settings, it does not interrupt by default.
- Add one more test: pass in the derived class array + write incompatible values.
- Don’t catch it downgrade——This is a logical error, not a data problem.
- non-generic collection (
ArrayList) will not throw this, but inTake out and force transfertime throwInvalidCastExceptiondon’t be confused.
2.4 RankException – Dimension mismatch
Rank is the number of dimensions of the array (int[,] → 2,int[][] → 1). This exception basically only appears inArray copy family methodsmiddle:
int[,] src = new int[2, 3];
int[] dst = new int[6];
src.CopyTo(dst, 0); // 💥 RankException(2 维拷不进 1 维)
int[][] jagged = { new[] { 1, 2 }, new[] { 3, 4 } };
int[,] rect = new int[2, 2];
Array.Copy(jagged, rect, 2); // 💥 RankException(jagged 的 Rank=1,且元素类型也不兼容)
// ✅ 先校验再按维遍历
void CopyChecked(Array src, Array dst)
{
if (src.Rank != dst.Rank) throw new ArgumentException("维度数必须一致");
for (int d = 0; d < src.Rank; d++)
if (src.GetLength(d) != dst.GetLength(d))
throw new ArgumentException($"第{d}维长度不等");
src.CopyTo(dst, 0);
}
Special attention:
- jagged array
T[][]≠ multidimensional arrayT[,]. The former is a “one-dimensional array within a one-dimensional array”, and each row can have different lengths and can be discontinuous on the managed heap; the latter is a single continuous block. Mutual copying between the two will lead to problems, and serialization (BinaryFormatter/old protocol) interoperability is often pitted. Array.CreateInstancecan makenon-zero lower boundarray(GetLowerBound(d) != 0), this kind of array will behave strangely when passed to many APIs that only recognize SZArray; useArray.IsSZArray(reflection/internal) or unified requirementsGetLowerBound(d)==0Be defensive.- APIs that only accept one-dimensional arrays (such as
Array.Sort、BinarySearch,mostBuffer/Marshaloperation) usually throws when passing multi-dimensional arrayArgumentExceptionrather thanRankException——Don’t do it just because you didn’t see itRankExceptionI thought the dimensions were okay. - High-performance scenarios are preferred
Span/Memory(Natural one-dimensional continuous)say goodbye to dimensionality problems completely.
2.5 InvalidOperationException – Collection modified during enumeration
The enumerator holds internally _versionthe collection will increase automatically every time it is added, deleted or modified;MoveNext() If version inconsistency is found, it will be thrown immediately. "Collection was modified; enumeration operation may not execute."
var list = new List { 1, 2, 3 };
foreach (var x in list)
if (x == 2) list.Remove(x); // 💥 下一轮 MoveNext 抛 InvalidOperationException
var dict = new Dictionary{["a"]=1};
foreach (var k in dict.Keys) // 会抛异常的是增删操作(Add、Remove、Clear),它们会递增 _version
dict.Remove(k); // 💥 抛异常,InvalidOperationException
// dict[k] = 2; // 更新已有键的值时不会递增 _version,因此枚举器在下一轮 MoveNext 检测到版本一致,不会抛出异常
// ✅ 1) 倒序索引遍历(删除不移位影响后续下标)
for (int i = list.Count - 1; i >= 0; i--)
if (list[i] == 2) list.RemoveAt(i);
// ✅ 2) 先快照再遍历(简单粗暴,小集合首选)
foreach (var x in list.ToList())
if (x == 2) list.Remove(x);
// ✅ 3) 用集合自身的高效批量 API
list.RemoveAll(x => x == 2); // 一次遍历、无额外分配
// ✅ 4) 收集再删除(跨多个集合/复杂条件)
var toRemove = new List();
foreach (var kv in dict) if (kv.Value < 0) toRemove.Add(kv.Key);
foreach (var k in toRemove) dict.Remove(k);
// ✅ 5) 真正的并发读写
var cd = new ConcurrentDictionary();
// 注意:ConcurrentDictionary 的枚举器是“某一时刻的快照”,修改不抛异常,
// 但可能看到重复/遗漏项——业务上仍需按幂等设计。
Special attention:
- Delayed execution is the invisible killer:
var q = list.Where(...)It’s just an expression tree. The actual enumeration may occur after several layers of calls, or even in another thread, when the original collection has already been changed. need as early as possible.ToList()/.ToArray()Materialize, or pass in an immutable collection. - Thread safety ≠ do not throw this exception: Multiple threads share one
DictionaryEven without modification, it may be thrown away due to damage to the internal structure.IndexOutOfRangeExceptionOr an infinite loop.ConcurrentDictionaryIt only guarantees its own consistency and does not guarantee “global quiescence during enumeration”. - Pitfalls of structure enumerators:
ListReturns struct if boxed into.GetEnumerator() IEnumerator(likeforeach (var x in (IEnumerable),every time)list) CurrentThey are all copies, and there may be a strange phenomenon of “always stopping at the first item”; at the same time, the modification detection is still in effect. - Other common sources of the same exception (to avoid misjudgment when searching logs):
Single()/First()empty collection,Queue.Dequeue/Stack.Popnull,IEnumeratornot yetMoveNextStudyCurrentLINQOrderByComparator violates contract, EF Core’sDbContextconcurrent use,TaskrepeatStart、yield returnIllegal status in iterator. - The most typical in ASP.NET Core: use the mutable collection injected as Scoped/Singleton as a global cache and update it during enumeration – or replace it with
ConcurrentDictionaryorlock+ Snapshot.
3. General checklist
- Classify first:bring
Index/Key/Rank/TypeMismatchof = bug;InvalidOperationException= status or concurrency issues. The first four categories are not recommended for production usetry/catchSwallowed, it should be allowed to explode and take context. - Logs must contain “boundary information”: Remember when throwing
index / Length / Count / key / actualType / Rank / GetLength(d)90% of out-of-bounds cases can be located by looking at this line. - Boundary use case single test: Empty set, single element, exactly full, one more, zero-length array, empty string, split result, non-zero lower bound array, interleaved array.
- Replace handwritten subscripts with language features:
foreach、^and..、Spanpattern matching,refReturn; used when subscripting is requiredfor (int i = 0; i < c.Count; i++)And cache the upper limit to local variables (not only to prevent concurrent tampering with the length, but also to help JIT eliminate boundary checks). - Dictionaries always go
TryGetValue/GetValueOrDefault; Need to use “calculate and insert if it does not exist”GetOrAddorGetValueRefOrAddDefault。 - Eliminate covariant arrays at the API design level:for parameters
IReadOnlyList/ReadOnlySpanreturn an immutable collection;params object[]Be careful with overloading the actual array type passed in. - concurrent: Mutable collections cannot be locked or thread bounded; use them if you need to share them.
Concurrent*orImmutable*;Anything that needs to be updated during enumeration must be “snapshot or new replacement”. - Debugging Tips: VS Lip
IndexOutOfRangeExceptionCheck “Break on this exception type” (it is sometimes not interrupted in the default settings); for occasional problems, useDebug.Assert(index >= 0 && index < arr.Length)assertion failure occurs earlier and with more complete information than exceptions.
4. A small summary
Although these five anomalies have different trigger points, theyThe essence is that the contract is broken, not “the data is bad”。
- IndexOutOfRangeException / KeyNotFoundException 是“Transgression and Absence”which means no verification is done before access;
- ArrayTypeMismatchException / RankException 是“Type does not match dimension”resulting from deferring information that should be guaranteed by the compiler or the caller to the runtime (covariant arrays, multi-dimensional array copies);
- InvalidOperationException is“Wrong status”mostly modifying the collection during enumeration or concurrently sharing mutable state.
Their common characteristics are:The location where the exception is thrown is often far away from the root cause.——No error will be reported at the assignment point, but an error will be reported at the writing point; the enumerator will only report an error in the next round of MoveNext; params object[] The behavior depends on whether the caller passes a literal or an existing array. therefore,Catching them as “recoverable errors” is almost always a mistake; the correct approach is to eliminate them at entry and boundaries.
When it comes to engineering practice, you can only remember three rules:
- Use Try* and length judgment to replace subscripts and indexers(TryGetValue, GetValueOrDefault, ElementAtOrDefault, reverse order traversal, RemoveAll);
- Use the type system to block covariance and dimensionality issues(Only IReadOnlyList is exposed to the outside world
/ ReadOnlySpan ,params object[] Treat it as read-only. If you want a copy, create a real object.[]generic code never converts T[] promoted to object[]); - Keep mutable collections from being locked or thread bounded, and snapshots or new replacements will be made during enumeration.。
Coupled with the boundary information that must be recorded in the log (Length/Count/key/Rank/real runtime type), the check “Interrupt when thrown” in VS, and the single test of empty collection and difference by one, the first four exceptions can be zero online, and the remaining InvalidOperationException can basically be converged by the concurrency design.
In one sentence:Anything that can be blocked by length judgment, Try* API, immutable snapshots, and generic immutability should not be left to the runtime.