CodeGym /課程 /C# SELF /認識 Indexer

認識 Indexer

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

1. 關於 collection 的幾句話

到目前為止我們都在用陣列——這是存放同型別多個元素最簡單的方式。但 C# 其實有超多更方便、更強大的 collection,可以解決各種需求。

Collection 就是可以存一堆其他物件的物件。跟陣列不一樣,collection 通常可以動態改變大小,還有很多方便的資料操作方法,針對不同情境也有最佳化。

主要的 collection 類型(快速瀏覽):

List<T> —— 動態陣列,可以自動增減長度:

List<string> names = new List<string>();
names.Add("阿列克斯");
names.Add("瑪莉亞");
Console.WriteLine(names[0]); // 阿列克斯
Console.WriteLine(names.Count); // 2

Dictionary<TKey, TValue> —— "key-value" 配對的 collection,每個 key 對應一個 value:

Dictionary<string, int> ages = new Dictionary<string, int>();
ages["阿列克斯"] = 25;
ages["瑪莉亞"] = 30;
Console.WriteLine(ages["阿列克斯"]); // 25

注意:上面例子我們用中括號 [] 來存取 collection 的元素——就跟陣列一樣!這就是 indexer 的功勞,今天我們就要聊這個。

這只是 collection 的初步認識——詳細的功能、差異和應用我們之後會再深入。現在你只要知道,collection 可以用中括號存取元素,這不是魔法,是語言的特殊機制。

2. Indexer

一般的 C# 物件都是用屬性和方法來操作。但如果你的物件本身就是個 mini-collection 呢?比如:

  • 你寫了一個 Week class,要根據數字回傳星期幾的名字:week[0] → "星期一"
  • 或是 Library class,可以用編號找書:library[3] → "向斯萬之路"

這樣用起來是不是超直覺?每次都寫 library.GetBookByIndex(3) 太囉嗦了,大家都想像用陣列一樣存取!

這時候 indexer 就派上用場啦。

Indexer —— 是 class 的一種特殊成員,讓你可以用中括號語法存取物件,就像陣列一樣: obj[0]obj["key"],等等。

indexer 外觀看起來像屬性,但沒有名字,參數寫在中括號裡。就像你平常寫 .Name,但這裡是 [i]

3. 有 indexer 的簡單 collection

來做個小 class,存你最愛的顏色。用字串陣列來存。如果沒有 indexer,就得寫個 GetColor(int i) 方法。但有 indexer 就很帥:


using System;

public class FavoriteColors
{
   // 私有欄位存顏色
   private string[] colors = new string[5];

   // Indexer:
   public string this[int index]
   {
       get
       {
           // 陣列邊界檢查(封裝!)
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("顏色索引錯誤!");

           return colors[index];
       }
       set
       {
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("顏色索引錯誤!");

           colors[index] = value ?? throw new ArgumentNullException(nameof(value));
       }
   }
}

class Program
{
   static void Main()
   {
       FavoriteColors favorites = new FavoriteColors();

       favorites[0] = "綠色";
       favorites[1] = "藍色";
       favorites[2] = "紅色";
       favorites[10] = "紫色"; // 會丟出例外!

       Console.WriteLine(favorites[1]); // 藍色
   }
}

這裡發生了什麼?

  • 我們做了一個私有陣列,外部不能直接亂動。
  • indexer 寫成 public string this[int index]this 表示這是物件本身的 indexer。
  • getset 裡做邊界檢查,不讓你越界或寫 null
  • 最後就可以像陣列一樣用 favorites[0] 存取。

4. Indexer 語法細節

語法跟屬性很像,但名字(像 Age)換成 this 加參數:


// Indexer 的簽名(通用範本)
[修飾詞] 回傳型別 this[索引型別 索引名稱]
{
   get { ... }
   set { ... }
}

範例:經典寫法

public class MyCollection
{
   private int[] data = new int[10];

   // 可讀寫的 indexer
   public int this[int index]
   {
       get { return data[index]; }
       set { data[index] = value; }
   }
}

Indexer 不只可以用 int

重點:indexer 不一定只能用 int。你可以用任何型別(只要 key 有意義):

public string this[string colorName]
{
   get { /* ... */ }
   set { /* ... */ }
}

比如電話簿 class,直接用名字查:

public class PhoneBook
{
   private Dictionary<string, int> entries = new Dictionary<string, int>();

   public int this[string name]
   {
       get
       {
           if (entries.ContainsKey(name))
               return entries[name];
           return null;
       }
       set
       {
           entries[name] = value;
       }
   }
}

關於 collection 跟 Dictionary<string, string> 怎麼運作,我之後會再講 :P

5. 實戰範例:文字裡的單字計數器

繼續進化我們的小程式。假設現在有個 class,可以計算每個單字在文字中出現幾次。很方便的是,使用者可以直接用中括號查單字出現次數:


using System.Collections.Generic;

public class WordCounter
{
   private Dictionary<string, int> counter = new Dictionary<string, int>();

   // 用字串(單字)當 indexer
   public int this[string word]
   {
       get
       {
           if (counter.ContainsKey(word))
               return counter[word];
           return 0; // 沒這個單字就回傳 0。
       }
       set
       {
           counter[word] = value;
       }
   }

   // 從字串加進單字計數
   public void AddWords(string text)
   {
       foreach (var word in text.Split(' ', System.StringSplitOptions.RemoveEmptyEntries))
       {
           if (counter.ContainsKey(word))
               counter[word]++;
           else
               counter[word] = 1;
       }
   }
}

// 在 Main:
var wc = new WordCounter();
wc.AddWords("媽媽 洗 桌子 洗 媽媽 爸爸");
Console.WriteLine($"'媽媽' 出現 {wc["媽媽"]} 次");
Console.WriteLine($"'桌子' 出現 {wc["桌子"]} 次");
Console.WriteLine($"'貓' 出現 {wc["貓"]} 次"); // 0

這有什麼實用價值? 這種寫法常用來做自訂 collection、記憶體庫、mapping(像 dictionary 跟 index),甚至自製 DSL(C# 裡的小語言)。

6. 限制與細節

indexer 很強大,但還是有幾個規則跟小陷阱(哪有那麼完美 XD)。

indexer 沒有名字

跟屬性不一樣,indexer 沒有名字,只有 this[參數型別] 這種簽名。如果你寫 public int MyIndexer[int i],編譯器會傻眼。只能用 this

不能有 static indexer

indexer 只能用在 class 實體,不能用在 static 成員。也就是說不能宣告 static int this[int i],因為 this 只指向物件本身。

可以用不同型別/參數數量 overload

你可以在同一個 class 裡寫多個 indexer,只要參數型別或數量不同。例如:

public string this[int i] { get { ... } set { ... } }
public string this[string key] { get { ... } set { ... } }

這是合法的,編譯器不會搞混——如果參數重複才會報錯。

一定要有 get 或 set

indexer 一定要有 getset。如果只想讀,就拿掉 set,只想寫就拿掉 get。通常兩個都會寫。

7. 實用價值與為什麼要學這個

  • indexer 在 資料 collection 裡超常用。很多 .NET class 都有:像 List<T>Dictionary<TKey,TValue>。你寫 list[2] 就是在用 indexer!
  • indexer 可以隱藏內部實作(封裝!),但給你一個直覺又熟悉的介面。用你 class 的人不用管你怎麼存資料,只要用 [index] 就好。
  • 你的 code 會變得簡潔又好懂——面試官(還有未來同事)都會很愛這種寫法。

屬性 vs Indexer:比較

屬性 Indexer
名字 有(例如 Name) 沒有(用 this[參數] 取代)
存取方式 用名字 用 index(或其他 key)
static 可以是 static 只能用在實體
一個 class 幾個 可以很多 可以很多,但參數簽名要不同
用途 存/取資料 mini-collection、關聯資料

8. 常見錯誤與小建議

錯誤 1: 想寫 static indexer。
不行啦——this[...] 只能用在物件上。

錯誤 2: 忘記檢查 index。
如果 getset 沒檢查邊界,程式可能直接爆掉。

錯誤 3: 參數型別搞混。
如果寫兩個參數一樣的 indexer,編譯器會報錯。

錯誤 4: 忘了寫 getset
如果要能讀又能寫,兩個都要有。

建議: 如果你的 class 包裝陣列——直接把存取都丟給 indexer。這樣又快又直覺。

9. 為什麼要學這個

indexer 讓物件的介面變得簡單、好懂又好用。你可以隱藏內部實作,但給開發者一個超方便的資料存取方式。

內建的 collection 就是這樣做的:stringList<T>Dictionary<TKey, TValue>Span<T> 等等。你寫 array[2]text[0],其實就是在用 indexer。

最重要的是——你現在也能寫出 自己的 class,像這些 collection 一樣靈活又簡潔。這就是專業又好讀的 code 的第一步啦!

2
任務
C# SELF, 等級 18, 課堂 0
上鎖
建立一個可以用星期幾作為索引的「Week」類別
建立一個可以用星期幾作為索引的「Week」類別
2
任務
C# SELF, 等級 18, 課堂 0
上鎖
帶有名稱索引子的電話簿
帶有名稱索引子的電話簿
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION