1. 前言
想像一下:你有個集合……但你想要很快拿到第三個、第七個,或是第零個元素。或是想把它們交換位置。陣列這種東西超簡單 — 用索引就搞定了(array[3])。那集合呢?不是每種集合都能這樣隨便用索引喔!
這時候 IList<T> 介面就登場啦 — 它是一個通用合約,要求集合必須支援用索引存取元素。簡單說:只要你的集合有實作 IList<T>,你就可以像操作陣列一樣用 索引 直接存取或改值。
比喻:
想像圖書館的卡片:每本書在書架上都有自己的編號,你隨時可以走過去拿「第三本」書。支援 IList<T> 的集合就是這種感覺。
2. IList<T> 的結構與方法
IList<T> 介面是很多集合的主角。它繼承自 ICollection<T>(而 ICollection<T> 又繼承 IEnumerable<T>,所以可以用 foreach 之類的迴圈跑),然後加上最重要的:索引操作。
介面繼承結構圖:
IEnumerable<T>
▲
│
ICollection<T>
▲
│
IList<T>
IList<T> 主要成員
| 成員 | 用途 |
|---|---|
|
用索引取得或設定元素 |
|
找出元素第一次出現的索引 |
|
在指定位置插入元素 |
|
用索引刪除元素 |
其他像 Add、Remove、Clear、Contains 這些,都是從 ICollection<T> 來的。
重點:Indexer(索引子)
IList<T> 最大的特色就是有 indexer。這是一種語法糖,讓你可以這樣寫:
var myList = new List<int> { 10, 20, 30 };
int secondValue = myList[1]; // 取得 20
myList[2] = 42; // 改第三個元素
3. 哪些集合有實作 IList<T>
在 .NET 標準函式庫裡,很多常見的資料結構都支援 IList<T>。來看看最熱門的幾個:
| 集合 | 可索引 | 說明 |
|---|---|---|
|
可以 | 動態陣列 |
|
可以 | 一般陣列都能用索引 |
|
可以 | 用於資料繫結(data binding) |
|
可以 | 有通知功能的清單 |
|
可以 | 集合的基底類別 |
注意:
LinkedList<T> 跟
HashSet<T>
不支援
IList<T>,因為它們沒有快速索引(沒錯,
LinkedList<T> 沒有
list[5] 這種東西!)。
4. IList<T> 使用範例
用索引取得和設定
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
IList<string> fruits = new List<string> { "蘋果", "香蕉", "梨子" };
// 取得第二個元素
string fruit = fruits[1];
Console.WriteLine(fruit); // 香蕉
// 替換第三個元素
fruits[2] = "橘子";
Console.WriteLine(fruits[2]); // 橘子
}
}
用索引插入和刪除
fruits.Insert(1, "奇異果"); // 在第二個位置插入 "奇異果"
// 現在清單是:"蘋果", "奇異果", "香蕉", "橘子"
fruits.RemoveAt(0); // 刪除第一個元素("蘋果")
// 現在清單是:"奇異果", "香蕉", "橘子"
查找元素的索引
int index = fruits.IndexOf("橘子"); // 回傳索引(2),找不到就 -1
if (index != -1)
Console.WriteLine("橘子在位置:" + index);
else
Console.WriteLine("橘子沒找到");
5. 實作細節與常見錯誤
用 IList<T> 時很容易踩到幾個「地雷」,尤其是忘記索引從零開始,還有集合長度就是目前元素數量。
例如,試著存取不存在的元素:
Console.WriteLine(fruits[100]); // IndexOutOfRangeException!
C# 的索引就像母雞下蛋:從零開始,不是從一開始。如果清單有 4 個元素,最後一個索引是 3。
還有要記得,不是每個 IList<T> 的實作都一樣快。像陣列或 List<T> 用索引超快(O(1)),但如果你自己用 linked list 實作一個集合然後硬加 IList<T>,那存取就會變慢。雖然標準函式庫不會這樣搞。
還有一點:如果你把陣列當 IList<T> 用,可以改元素,但不能改陣列長度。像 Add、Remove、Insert 這些方法對陣列會丟 NotSupportedException。
int[] myArray = { 1, 2, 3 };
IList<int> listView = myArray; // Upcast
listView[0] = 42; // OK!
listView.Add(99); // 會丟 NotSupportedException
6. 實務應用與為什麼要這樣設計
在實際專案裡,幾乎每兩個集合就有一個是實作 IList<T>,因為用索引存取、改值、插入、刪除都超方便。舉例來說:
- WPF 或 WinForms 的 ViewModel 裡的介面屬性,UI 元素清單會綁定到這種集合。
- 排序、搜尋、置換等演算法,需要用索引存取元素。
- 資料匯入/匯出模組,會操作動態物件清單。
面試時,問 IEnumerable<T>、ICollection<T> 跟 IList<T> 差在哪,超常見。你只要知道每一層負責什麼,就能很穩地跟面試官解釋為什麼 HashSet<T> 沒有實作 IList<T>(因為唯一性比順序和索引重要!)。
GO TO FULL VERSION