C#では、クラスや構造体のインスタンスに対して、配列と同じようにブラケット [] を使用してデータにアクセスする機能を提供できます。これを「インデクサ(Indexer)」と呼びます。
インデクサを使用することで、内部にコレクションを持つクラス(ラッパークラスなど)の直感的な操作性が向上します。ここでは、社員情報を管理するクラスを題材に、整数インデックスによるアクセスと、社員ID(文字列)によるアクセスの両方をオーバーロード(多重定義)する方法について解説します。
インデクサの基本構文
インデクサはプロパティに似ていますが、名前の代わりに this キーワードを使用し、引数を取る点が異なります。
public 戻り値の型 this[インデックスの型 引数名]
{
get { /* 取得時の処理 */ }
set { /* 設定時の処理 (valueキーワードで値を受け取る) */ }
}
実践的なコード例:社員名簿の管理
以下のコードは、List<Employee> を内部に持つ EmployeeRegistry クラスに対し、以下の2つのインデクサを実装した例です。
int型のインデクサ: リストの順番(インデックス)で要素を取得する(読み取り専用)。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など)よりも直感的で可読性の高いコードを提供できる強力な機能です。
