1. 多參數索引子
簡單的索引子(像是 this[int index])很適合你要用整數 index 存取元素時用——就像陣列一樣。但 C# 其實可以做更多:你可以用多個參數、不同型別的參數,給 get 跟 set 設不同的存取修飾詞,甚至在同一個 class 裡實作多個索引子(只要簽名不同)。
而且新版 C# 還有更多方便的功能,讓你寫索引子的時候更順手,語法糖也讓程式碼更精簡。
索引子其實不一定只能吃一個 index,也不一定只能是 int。參數你自己決定——你可以有多個不同型別的參數,只要你的需求有需要。
public class ChessBoard
{
private string[,] board = new string[8, 8];
// 有兩個參數的索引子!
public string this[int row, int col]
{
get { return board[row, col]; }
set { board[row, col] = value; }
}
}
現在我們可以這樣寫:
ChessBoard chess = new ChessBoard();
chess[0, 0] = "車";
chess[7, 7] = "國王";
string piece = chess[0, 0]; // "車"
這種寫法超適合矩陣、二維地圖、桌遊、複雜集合。
2. 不同型別參數的索引子
你的索引子不只可以吃 int,也可以吃任何你覺得合理的型別。重點是要符合你的需求。
using System.Collections.Generic;
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public string Position { get; set; }
}
public class EmployeeCollection
{
private List<Employee> employees = new List<Employee>();
// 用員工名字(string)當索引
public Employee this[string name]
{
get
{
foreach (var employee in employees)
{
if (employee.Name == name)
return employee;
}
return null; // 或者你可以丟例外
}
set
{
for (int i = 0; i < employees.Count; i++)
{
if (employees[i].Name == name)
{
employees[i] = value;
return;
}
}
// 如果沒有這個名字的員工——就加一個新的
employees.Add(value);
}
}
// 兼容性——用數字 index 的索引子
public Employee this[int index]
{
get { return employees[index]; }
set { employees[index] = value; }
}
}
var company = new EmployeeCollection();
company[0] = new Employee { Name = "伊凡", Age = 30, Position = "程式設計師" };
company[1] = new Employee { Name = "瑪麗亞", Age = 25, Position = "設計師" };
Employee employee = company["伊凡"];
company["彼得"] = new Employee { Name = "彼得", Age = 28, Position = "測試員" };
注意:如果你有多個索引子——他們的簽名(參數型別和數量)一定要不一樣。
3. get 跟 set 不同存取修飾詞
有時候你只想讓人讀不能寫(或反過來)。在 C# 裡你可以給索引子的 get 跟 set 設不同的存取修飾詞。
public class SecureEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get { return employees[index]; }
internal set { employees[index] = value; }
}
}
這很常用來保護集合不被亂改,讓 class 更好控管。
4. Read-only 跟 Write-only 索引子
有時候你只想讓人讀或只讓人寫。
public class ReadOnlyEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get { return employees[index]; }
// 沒有 set——不能改!
}
}
public class WriteOnlyEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
set
{
employees.Insert(index, value);
}
// 沒有 get——不能讀!
}
}
實務上 "write-only" 幾乎沒人用:通常都是 "read-only",像是 class 只讓外部看不能改資料。
5. 有邊界檢查和邏輯的索引子
寫得好的例子不只要能用 index 存取,還要能正確處理超出範圍、找不到資料等例外狀況。
public class SafeEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get
{
if (index < 0 || index >= employees.Count)
throw new IndexOutOfRangeException("這個 index 沒有員工!");
return employees[index];
}
set
{
if (index < 0 || index >= employees.Count)
throw new IndexOutOfRangeException("不能取代不存在的員工!");
employees[index] = value;
}
}
}
你可以回傳 null,或用現代的缺值處理 pattern——看你的應用邏輯怎麼設計。
6. 奇特參數的索引子
你可能會遇到有些集合的索引子吃很特別的型別:enum、自己定義的 struct,甚至多個不同型別的參數。
public enum Department { IT, HR, Finance, Marketing }
public class DepartmentEmployeeCollection
{
private Dictionary<Department , Employee> departmentLeads = new Dictionary<Department , Employee>();
public Employee this[Department department]
{
get { return departmentLeads.TryGetValue(department, out var employee) ? employee : null; }
set { departmentLeads[department] = value; }
}
}
var company = new DepartmentEmployeeCollection();
company[Department.IT] = new Employee { Name = "安娜", Age = 35, Position = "IT 負責人" };
Employee itLead = company[Department.IT];
這種寫法很適合你有唯一識別碼或型別和值一一對應的情境——直觀又型別安全。
7. 現代功能:Range 跟 Index
自從 Range 跟 Index 型別在 C# 8 出現後,索引子多了新玩法,可以處理 範圍 跟 從尾端算的 index:
public class SmartArray
{
private int[] numbers = Enumerable.Range(0, 100).ToArray();
public int[] this[Range range] => numbers[range];
public int this[Index index] => numbers[index];
}
// 用法:
var smart = new SmartArray();
int[] middle = smart[20..30]; // 第 20 到 29 個元素
int last = smart[^1]; // 最後一個元素
如果你自己實作集合,支援 Range 跟 Index 會讓它超級「原生」又好用。
8. 屬性 vs 索引子
要搞懂什麼時候該用屬性、什麼時候該用索引子,對比一下他們的特性會很有幫助。
- 屬性適合用名字存取物件的個別特性:person.Name、car.Speed。
- 索引子適合用 key 存取集合或結構的元素:employees[0]、phoneBook["伊凡"]。
- 屬性一定有名字,而且一個 class 只能有一個同名屬性。
- 索引子用 this,而且可以有多個不同簽名的版本。
- 屬性可以是 static,索引子不行,因為 this 一定指向某個物件實例。
9. 用索引子常見錯誤
錯誤一:沒檢查邊界。
如果沒檢查 index 有沒有超出陣列範圍,會在你沒想到的時候遇到 IndexOutOfRangeException。
錯誤二:沒處理索引子回傳的 null。
如果用字串索引找不到元素時回傳 null,結果呼叫端沒檢查就直接用,會爆 NullReferenceException。
錯誤三:索引子簽名重複。
C# 不允許你有兩個參數一樣的索引子。
錯誤四:set 裡的邏輯不直觀。
如果 set 裡是「沒有就加」而不是「取代」,會讓人搞混。這種設計要寫清楚、註解清楚。
10. 實務應用與結語
在實際專案裡,索引子常用來做專用集合、快取、有額外邏輯的字典、矩陣和多維資料結構。它們讓程式碼更直觀易懂——不用寫 GetElementByIndex(5),直接 collection[5] 就好。
記得:索引子的設計要符合你的 class 本質。如果 class 不是集合或資料結構,通常不需要索引子。但如果你的 class 是在管理一堆元素,索引子會讓它用起來更順手、更自然。
GO TO FULL VERSION