【C#】クラスを配列のように扱うインデクサ(Indexer)の定義とオーバーロード

C#では、クラスや構造体のインスタンスに対して、配列と同じようにブラケット [] を使用してデータにアクセスする機能を提供できます。これを「インデクサ(Indexer)」と呼びます。

インデクサを使用することで、内部にコレクションを持つクラス(ラッパークラスなど)の直感的な操作性が向上します。ここでは、社員情報を管理するクラスを題材に、整数インデックスによるアクセスと、社員ID(文字列)によるアクセスの両方をオーバーロード(多重定義)する方法について解説します。


目次

インデクサの基本構文

インデクサはプロパティに似ていますが、名前の代わりに this キーワードを使用し、引数を取る点が異なります。

public 戻り値の型 this[インデックスの型 引数名]
{
    get { /* 取得時の処理 */ }
    set { /* 設定時の処理 (valueキーワードで値を受け取る) */ }
}

実践的なコード例:社員名簿の管理

以下のコードは、List<Employee> を内部に持つ EmployeeRegistry クラスに対し、以下の2つのインデクサを実装した例です。

  1. int 型のインデクサ: リストの順番(インデックス)で要素を取得する(読み取り専用)。
  2. string 型のインデクサ: 社員IDをキーとして要素を取得・設定する(Upsert動作)。
using System;
using System.Collections.Generic;
using System.Linq;

namespace PersonnelSystem
{
    // 社員データ(C# 9.0以降の record型を使用)
    public record Employee(string Id, string Name, string Department);

    // 社員名簿クラス
    public class EmployeeRegistry
    {
        // 内部データストア
        private readonly List<Employee> _employees = new();

        // 1. インデックス番号(int)によるアクセス
        // 記述を簡潔にするため、式形式のメンバー定義(Expression-bodied members)を使用
        // 配列のように registry[0] でアクセス可能になります。
        public Employee this[int index] => _employees[index];

        // 2. 社員ID(string)によるアクセス
        // ディクショナリのように registry["EMP001"] でアクセス可能になります。
        public Employee this[string employeeId]
        {
            get
            {
                // 指定されたIDを持つ社員を返す(存在しない場合は例外発生)
                return _employees.First(e => e.Id == employeeId);
            }
            set
            {
                // リスト内に同じIDが存在するか検索
                var index = _employees.FindIndex(e => e.Id == employeeId);

                if (index < 0)
                {
                    // 存在しない場合は新規追加
                    _employees.Add(value);
                }
                else
                {
                    // 存在する場合は情報を上書き(更新)
                    _employees[index] = value;
                }
            }
        }

        // 現在の登録件数を取得するプロパティ
        public int Count => _employees.Count;
    }

    class Program
    {
        static void Main()
        {
            var registry = new EmployeeRegistry();

            // stringインデクサ(set)を使用してデータを登録
            // IDが存在しないため、新規追加されます。
            registry["EMP-001"] = new Employee("EMP-001", "山田 太郎", "開発部");
            registry["EMP-002"] = new Employee("EMP-002", "鈴木 花子", "営業部");

            // stringインデクサ(set)を使用してデータを更新
            // IDが存在するため、既存のレコードが上書きされます。
            registry["EMP-001"] = new Employee("EMP-001", "山田 太郎", "研究開発部"); // 部署異動

            // intインデクサ(get)を使用して先頭のデータを取得
            var firstEmployee = registry[0];
            Console.WriteLine($"[Index 0] {firstEmployee.Name} ({firstEmployee.Department})");

            // stringインデクサ(get)を使用してID指定でデータを取得
            var targetEmployee = registry["EMP-002"];
            Console.WriteLine($"[ID search] {targetEmployee.Name} ({targetEmployee.Department})");
        }
    }
}

実行結果

[Index 0] 山田 太郎 (研究開発部)
[ID search] 鈴木 花子 (営業部)

技術的なポイント

1. インデクサのオーバーロード

メソッドと同様に、引数の型や個数が異なれば複数のインデクサを定義できます。上記の例では、int(順序アクセス)とstring(キー検索)を使い分けています。これにより、利用シーンに応じた柔軟なアクセス手段を提供できます。

2. getアクセサとsetアクセサ

  • get: 値を取得する際に呼ばれます。
  • set: 値を代入する際に呼ばれます。代入された値は value というキーワードで参照できます。
  • 片方のみを実装することで、「読み取り専用」や「書き込み専用」のインデクサを作成することも可能です(上記の int インデクサは get のみ定義しているため読み取り専用です)。

3. 式形式のメンバー定義

C# 6.0以降、プロパティやインデクサの中身が単一の式である場合、=>(アロー演算子)を使用して簡潔に記述できます。

// 通常の書き方
public Employee this[int index] { get { return _employees[index]; } }

// 式形式の書き方
public Employee this[int index] => _employees[index];

まとめ

インデクサを適切に実装することで、コレクションを内包するクラスの利便性が高まります。特に「IDでの検索」や「順序での取得」といった操作が頻繁に行われるクラスにおいては、メソッド呼び出し(GetByIdなど)よりも直感的で可読性の高いコードを提供できる強力な機能です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次