Exception Handling

時間已經到傍晚 6 點,此刻的你正如同洩了氣的氣球軟軟的攤在桌上

主管 : 等一下可以幫我釐清…

你 :

1
2
3
4
5

System.InvalidOperationException: Operation is not valid due to the current state of the object.
at System.Collections.Generic.List`1.Enumerator.get_Current()
at Me.Program.Main(String[] args) in C:\Me\Program.cs:line 12

主管 : ???

你 : (。ŏ_ŏ)

Image



💤 InvalidOperationException 的本質是什麼?

「目前物件的狀態,不允許你執行這個動作。」

這種錯誤不是語法錯、也不是參數錯,而是「物件處於此狀態,你要求它做不該做的事。」



💤 常見觸發情境


1️⃣ 修改集合時,同時在迭代(foreach)

1
2
3
4
5
var list = new List<int> { 1, 2, 3 };
foreach (var item in list)
{
list.Add(4); // ⚠️ InvalidOperationException
}

🔍 原因:集合在被列舉時結構被改變(增刪元素),列舉器狀態失效。

往底層一點點看的話

在 .NET 中,當你使用 foreach,它其實會背後呼叫 List 的 GetEnumerator() 方法,並回傳一個 Enumerator(列舉器) 結構。這個列舉器內部長得像這樣(簡化版):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

struct ListEnumerator<T>
{
private List<T> _list;
private int _index;
private int _version;

public bool MoveNext()
{
if (_version != _list._version)
throw new InvalidOperationException("Collection was modified");

_index++;
return _index < _list.Count;
}
}

每次用 Add()、Remove()、Clear() 這類會改變集合內容的方法,List 內部的 _version 會自動 +1,像是這樣:

1
2
3
4
5
6
7

public void Add(T item)
{
_version++;
_items[_size++] = item;
}

比對 _version 是為了「資料一致性」與「避免難以預測的錯誤」

1
2
3
4
5
6
7

var list = new List<int> { 1, 2, 3 };
foreach (var item in list)
{
list.Remove(2); // 若允許移除,整個列舉就會錯亂
}

這會讓列舉器搞混它目前在哪個位置(index),例如:

  1. 它原本指向 index 1(值是 2)
  2. 你刪除了 2,後面元素前移
  3. 列舉器接下來會跳到 index 2(原本是 3),但現在 index 2 是 不存在 或是錯的值!

這時就可能會:跳過元素 –> 重複處理某些元素 –> 超出邊界 / 或者還是能繼續跑,但資料早就已經錯了!

.NET 為了避免這種難 debug 的錯誤,乾脆直接說:「只要你改過 List,我就直接不讓你繼續跑 foreach」,這叫 Fail-Fast 設計原則,快速讓錯誤浮現。


2️⃣ LINQ 查詢中使用 Single() 找不到或超過一筆

1
2
var nums = new List<int> {1, 2, 2};
var result = nums.Single(x => x == 2); // ⚠️ 兩筆符合,會拋例外

🔍 Single() 是在幫你做「資料唯一性驗證」。一旦有「兩筆以上」或「完全沒有」資料符合條件,就丟 InvalidOperationException。

找不到元素時:
“Sequence contains no matching element”

有超過一個時:
“Sequence contains more than one matching element”


3️⃣ 將 Nullable 的 null 轉型為基礎型別

int? n = null;
int x = n.Value;

你對一個有特定限制條件的「合法變數」,做了不合規的操作。你是對一個本身是 struct、有實體存在的變數,去硬拿一個不存在的,所以 CLR(.NET Runtime)才會說:「這不是 null 指標錯誤,而是操作不合法(Invalid Operation)」



☘️ 結語

InvalidOperationException,是提醒你:不是什麼時候都能做什麼事。
換個角度,換種方式,它就能正常運行,甚至跑得更好。

程式也是,人也是。不是不能做,而是要等對的時機,進入對的狀態。