1. 痛苦的進化史
你一定遇過這種情況:不只要遍歷集合,還要知道每個元素的編號。比如要輸出帶編號的清單、只改變非零索引的元素,或是每三個做一次什麼。在一般的 for 迴圈裡超簡單:
// 超熟悉,C# 經典寫法:
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine($"{i}: {list[i]}");
}
但如果你開始寫 LINQ,突然就……失去索引了!那些漂亮的 .Where、.Select、.OrderBy 都只給你元素本身,沒有編號。當然你會想這樣寫:
list.SelectWithIndex((item, index) => ...);
但標準 LINQ 根本沒有 SelectWithIndex 這種方法。雖然 Select 有 overload 可以拿到 index,但……如果你只想用索引又不想轉換或投影,就得自己多寫一堆 .Select,讓原本漂亮的 LINQ 變得很亂很難懂。
在 .NET 9 之前大家怎麼活下來的
以前 C# 程式設計師有自己的小撇步:
var result = list.Select((item, index) => new { item, index });
還有跟其他 LINQ 方法混搭,但總覺得不夠「原生」,也沒那麼美觀。
2. LINQ 新方法 Index — 到底是什麼?有什麼用?
官方說明
在 .NET 9,開發團隊終於聽到成千上萬(好啦,Twitter 上幾萬人也不少啦)的 C# 工程師哀號——加了一個新的 LINQ 擴充方法 Index。你可以在 .NET 9 官方文件 看到這樣的描述:
Index() 會幫每個序列元素加上從零開始的編號,回傳 (值, 索引) 的 pair,不用自己手動 new 匿名物件,也不用寫 .Select((item, idx) => new { item, idx })。
超方便——Index 直接給你每個元素和它的索引。
方法簽名
public static IEnumerable<(T Element, int Index)> Index<T>(this IEnumerable<T> source);
白話翻譯: 每個集合元素你都會拿到一個 tuple——Element 跟它的 Index。就這樣。再也不用自己 new 匿名型別。
3. Index 方法的使用範例
超簡單範例
來個最愛水果清單吧。
var fruits = new List<string> { "蘋果", "香蕉", "柳橙", "奇異果" };
foreach (var (fruit, idx) in fruits.Index())
{
Console.WriteLine($"{idx}: {fruit}");
}
輸出:
0: 蘋果
1: 香蕉
2: 柳橙
3: 奇異果
就這麼簡單!你可以很優雅地拿到「索引-值」pair,然後在任何 LINQ 查詢裡用。
跟其他 LINQ 方法一起用
Index 完全是 LINQ 家族一員!可以直接插進「鏈式」裡用。
範例 1:只選索引是奇數的元素
var numbers = Enumerable.Range(10, 10); // 10, 11, ... 19
var oddIndexes = numbers.Index()
.Where(pair => pair.Index % 2 == 1)
.Select(pair => pair.Element);
Console.WriteLine(string.Join(", ", oddIndexes));
輸出:
11, 13, 15, 17, 19
範例 2:只改變索引是偶數的元素
var users = new List<string> { "安娜", "伊戈爾", "卡佳", "丹尼斯" };
var modified = users.Index()
.Select(pair => pair.Index % 2 == 0 ? pair.Element.ToUpper() : pair.Element.ToLower());
foreach (var name in modified)
Console.WriteLine(name);
輸出:
安娜
伊戈爾
卡佳
丹尼斯
範例 3:根據索引合併其他集合(像 zip)
var ids = new[] { 101, 102, 103 };
var names = new[] { "Alice", "Bob", "Charlie" };
var merged = names.Index()
.Join(ids.Index(),
namePair => namePair.Index,
idPair => idPair.Index,
(namePair, idPair) => (idPair.Element, namePair.Element));
foreach (var (id, name) in merged)
Console.WriteLine($"{id}: {name}");
輸出:
101: Alice
102: Bob
103: Charlie
4. Index 在實際應用裡
來幫我們的「永遠」教學專案加個新功能:把所有學生清單和他們的編號一起列出來(比如讓使用者可以用編號選學生)。
範例:列出帶編號的學生清單
假設我們已經有 Student 類別:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
來個小學生清單:
var students = new List<Student>
{
new Student { Name = "達莎", Grade = 5 },
new Student { Name = "彼佳", Grade = 3 },
new Student { Name = "沃瓦", Grade = 4 },
new Student { Name = "奧莉亞", Grade = 5 }
};
現在用 Index 漂亮地列出所有學生和編號:
foreach (var (student, idx) in students.Index())
{
Console.WriteLine($"{idx + 1}. {student.Name} — 成績: {student.Grade}");
}
這裡
idx + 1 是為了讓編號從 1 開始,比較直覺。
結果:
1. 達莎 — 成績: 5
2. 彼佳 — 成績: 3
3. 沃瓦 — 成績: 4
4. 奧莉亞 — 成績: 5
實用性:現在如果使用者想用編號選學生——都準備好了!程式碼更簡單,完全不用「手動」計數器,超好讀,bug 也少。
5. 比較:Index 比舊方法帥在哪?
.NET 9 之前:舊風格
以前要同時拿到元素和索引,只能用 .Select((item, index) => ...) overload,還要 new 匿名型別:
var withIndexes = students.Select((student, index) => new { student, index });
要拿欄位還得寫 .student、.index——還是匿名型別,沒有漂亮的 tuple 名稱。
.NET 9 之後:21 世紀新風格
現在——不用再煩惱欄位、匿名型別。超直觀:
foreach (var (student, idx) in students.Index())
{
// 開箱即用,直覺、簡單又漂亮
}
程式碼更乾淨。更少 code、更少錯誤。超適合 LINQ 長鏈,不用再想 overload。
6. 使用細節與注意事項
適用範圍
Index 可以用在任何實作 IEnumerable<T> 的物件上。也就是說——所有常見集合、陣列、LINQ 查詢結果都可以。
Index 回傳什麼型別?
它回傳的是 tuple 列舉,第一個元素是物件本身(通常叫 Element),第二個是 Index(型別 int)。C# 現代 tuple 語法讓你可以直接寫 foreach (var (element, index) in ...),兩個值一次拿到。
可以用 Query Syntax 嗎?
不行,Index 是擴充方法,query syntax(SQL 風格 LINQ)不能直接用。像這樣會失敗:
// 不行!
var query = from s in students.Index() select ...;
如果想混用,只要把方法包進括號,然後當一般集合用就好:
var query = from pair in students.Index()
where pair.Index > 1
select pair.Element;
索引永遠從零開始
Index 一定從 0 開始,就像 C# 裡大多數東西。如果你想從 1 開始——自己加 1 就好。
8. 錯誤與細節——要注意什麼?
很多新手一開始會搞混 Index 跟 .Select((item, index) => ...) overload。常見錯誤有:
— 想在 query syntax 用 Index:「怎麼不能用?」——它只能當擴充方法用。
— 以為索引會從 1 開始,其實是 0。
— 以為 Index 會改變原本的集合——其實跟所有 LINQ 方法一樣,它會回傳新序列,不會動到原本的(immutable 風格)。
還有一個有趣的點:如果集合是「lazy」的(提醒一下:LINQ 查詢預設就是 lazy),Index 也是邊取邊算索引,不會預先算好。這超適合處理很大甚至無限的序列——索引永遠正確,也不會炸掉你的記憶體。
GO TO FULL VERSION