yield return 到底做了什麼?從 Enumerator 理解它的運作方式

前言 上一篇文章,我理解了 foreach 的運作方式。原來 foreach 並不知道 List、Dictionary 或 Queue 的內部實作,它只會透過 IEnumerator 一次取得一個元素。理解這件事情後,我又開始好奇另一件事。 Enumerator 到底是誰寫的? 如果每一個 Collection 都要自己實作 IEnumerator,不是很麻煩嗎? 如果沒有 yield return 假設今天我要建立一個可以被 foreach 遍歷的物件: public class NumberCollection { } 如果沒有 yield return,那我就必須自己建立一個 Enumerator。 public class NumberEnumerator : IEnumerator<int> { public int Current => ... object IEnumerator.Current => Current; public bool MoveNext() { ... } public void Reset() { ... } public void Dispose() { } } 還要讓 Collection 回傳這個 Enumerator。 ...

July 23, 2026 · 2 min · Yen Tsai

為什麼 foreach 可以遍歷所有 Collection?從設計角度理解 IEnumerable

前言 上一篇文章,我開始理解不同 Collection 存在的原因。Array、List、Dictionary、Queue、Stack 各自解決不同的資料管理問題,底層實作也不一樣。 理解這件事後,我突然想到另一個問題。 如果每一種 Collection 都長得不一樣,為什麼 foreach 卻能遍歷所有 Collection? 例如: List<int> list = new() { 1, 2, 3 }; foreach (var item in list) { Console.WriteLine(item); } 可以正常運作。換成 Dictionary: Dictionary<int, string> dictionary = new() { { 1, "Jack" }, { 2, "Amy" } }; foreach (var item in dictionary) { Console.WriteLine(item); } 也完全沒問題,甚至 Queue、Stack、HashSet 都能使用 foreach。 它們明明是不同的資料結構,foreach 又是怎麼做到的? 如果 foreach 必須認識每一種 Collection 我試著站在 .NET Framework 設計者的角度思考。 假設沒有任何共同規範,那 foreach 要怎麼設計? 最直接的方法,大概會變成: 如果是 List 就用 List 的方式遍歷 如果是 Dictionary 就用 Dictionary 的方式遍歷 如果是 Queue 就用 Queue 的方式遍歷 ... 問題很快就出現了:每新增一種 Collection,foreach 就必須修改一次。如果有人自己寫了一個新的 Collection,foreach 也不知道該怎麼遍歷它。 ...

July 23, 2026 · 2 min · Yen Tsai

為什麼 Array 無法解決所有問題?我是如何理解 .NET Collection 設計的

前言 以前我學 Collection,就是背: List 是動態陣列 Dictionary 是 Hash Table Queue 是 FIFO Stack 是 LIFO 但我一直沒有想通一個問題。 既然 Array 已經可以存很多資料了,為什麼 .NET 還要設計這麼多 Collection? 如果世界只有 Array 假設 .NET 只有 Array。 Student[] students = new Student[100]; 其實很多事情都做得到,例如: 存放資料 修改資料 使用索引快速取得資料 Array 還有一個很大的優點:透過索引取出指定元素。 students[50] 時間複雜度是: O(1) 但當需求開始改變,Array 的限制也會慢慢出現。 當資料超出原本預留的空間 假設: Student[] students = new Student[100]; 突然第 101 個學生來了。 Array 的長度建立後就固定了。要放進第 101 筆資料,只能: 建立新的 Array ↓ Copy 所有資料 ↓ 丟掉舊的 Array 這時我開始思考。 如果有一個容器可以自己長大,不就好了? 這正是 List<T> 想解決的問題。它的內部仍然使用 Array,但容量不足時,會建立更大的 Array,再把原有元素複製過去。 ...

July 23, 2026 · 2 min · Yen Tsai

C# Generic(泛型)- 我真正理解 Generic 的方式

前言 以前學 Generic,我只記住一句話: Generic 可以重複利用程式碼。 這句話沒有錯,但一直讓我無法真正理解 Generic 的設計目的。 直到我開始站在 Framework 設計者 的角度思考,我才發現: Generic 真正想解決的是「保留真正的型別」。 如果沒有 Generic,我會怎麼設計? 假設今天要交換兩個變數。 第一個想到的方法就是每個型別都寫一份。 void Swap(int a, int b) { } void Swap(string a, string b) { } void Swap(Student a, Student b) { } 問題就在,如果有 100 種型別,就要寫 100 個版本。 因此想要有個方法能夠接受任意型別的參數。 那全部改成 object 不就好了? 第二個想到的方法就是: void Swap(object a, object b) { } 這方法能夠接受所有型態,但是會有問題。 Value Type 需要 Boxing int x = 10; object obj = x; CLR 必須建立一個新的 Heap Object,因此會產生 Boxing。 ...

July 23, 2026 · 2 min · Yen Tsai