CodeGym /課程 /C# SELF /C# 進階索引子:參數、修飾詞、現代功能

C# 進階索引子:參數、修飾詞、現代功能

C# SELF
等級 18 , 課堂 1
開放

1. 多參數索引子

簡單的索引子(像是 this[int index])很適合你要用整數 index 存取元素時用——就像陣列一樣。但 C# 其實可以做更多:你可以用多個參數、不同型別的參數,給 getset 設不同的存取修飾詞,甚至在同一個 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# 裡你可以給索引子的 getset 設不同的存取修飾詞。


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

自從 RangeIndex 型別在 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]; // 最後一個元素

如果你自己實作集合,支援 RangeIndex 會讓它超級「原生」又好用。

8. 屬性 vs 索引子

要搞懂什麼時候該用屬性、什麼時候該用索引子,對比一下他們的特性會很有幫助。

  • 屬性適合用名字存取物件的個別特性:person.Namecar.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 是在管理一堆元素,索引子會讓它用起來更順手、更自然。

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