CodeGym /課程 /C# SELF /LINQ 新方法: Index

LINQ 新方法: Index

C# SELF
等級 32 , 課堂 3
開放

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 也是邊取邊算索引,不會預先算好。這超適合處理很大甚至無限的序列——索引永遠正確,也不會炸掉你的記憶體。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION