1. 複数パラメータのインデクサ
シンプルなインデクサ(例:this[int index])は、配列みたいに整数インデックスで要素にアクセスしたい時に便利。でもC#ならもっと色々できる!複数パラメータや異なる型のパラメータ、getとsetで違うアクセス修飾子を使ったり、シグネチャが違えば1つのクラスに複数インデクサもOK。
さらに新しいC#では、インデクサを使いやすくする追加機能も増えてて、シンタックスシュガーでコードもスッキリ書けるよ。
インデクサは必ずしも1つのint型インデックスだけを受け取る必要はない。パラメータは自分で決めてOKだし、必要なら異なる型の複数パラメータも使えるよ。
public class ChessBoard
{
private string[,] board = new string[8, 8];
// 2つのパラメータのインデクサ!
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]; // "ルーク"
こういうやり方はマトリックスや2Dマップ、ボードゲーム、複雑なコレクションにピッタリ。
2. 異なる型のパラメータを持つインデクサ
インデクサのパラメータはint型だけじゃなく、ロジックに合ってれば他の型でもOK。
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; // または例外を投げてもOK
}
set
{
for (int i = 0; i < employees.Count; i++)
{
if (employees[i].Name == name)
{
employees[i] = value;
return;
}
}
// その名前の社員がいなければ新規追加
employees.Add(value);
}
}
// 互換性のための数値インデックスのインデクサ
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; }
}
}
こうすると、コレクションの不正な変更を防げて、クラスの制御性が高まるよ。
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」はよくあるよ、クラス外からは見るだけでインデクサ経由でデータ変更不可にしたい時とかね。
5. 境界チェックやロジック付きインデクサ
ちゃんとした例では、インデックスアクセスだけじゃなく、範囲外や検索失敗などの例外もちゃんと処理しよう。
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("そのインデックスの社員はいません!");
return employees[index];
}
set
{
if (index < 0 || index >= employees.Count)
throw new IndexOutOfRangeException("存在しない社員は置き換えできません!");
employees[index] = value;
}
}
}
nullを返したり、最近のパターンで「値なし」を処理するのもアリ。アプリのロジック次第だね。
6. ちょっと変わったパラメータのインデクサ
インデクサのパラメータにenumや独自構造体、複数の異なる型を使うコレクションもあるよ。
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];
こういうのは、ユニークなIDや型と値の明確な対応がある時に便利で、分かりやすくて型安全だよ。
7. 最新機能: RangeとIndex
RangeとIndex型がC# 8で登場して、インデクサで範囲や末尾からのインデックスも扱えるようになったよ:
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みたいに。
- インデクサはコレクションや構造体の要素にキーでアクセスするのに向いてる:employees[0]、phoneBook["イワン"]とか。
- プロパティは必ず名前があって、同じクラスに同じ名前のプロパティは1つだけ。
- インデクサはthisキーワードを使い、シグネチャが違えば複数持てる。
- プロパティはstaticにもできるけど、インデクサはできない。thisは常にインスタンスを指すから。
9. インデクサでよくあるミス
ミス1: 境界チェックなし。
インデックスが配列の範囲外でもチェックしないと、思わぬタイミングでIndexOutOfRangeExceptionが出るよ。
ミス2: インデクサからのnullを考慮しない。
文字列インデクサで要素がなかった時にnullが返るのに、呼び出し側がそのまま使うとNullReferenceExceptionになる。
ミス3: インデクサのシグネチャ重複。
C#では同じパラメータセットのインデクサを2つ作れないよ。
ミス4: setアクセサの分かりにくいロジック。
setで「なければ追加」みたいなロジックだと混乱しやすい。こういうのは明示的にして、ちゃんとドキュメント書こう。
10. 実践例とまとめ
実際のプロジェクトでは、インデクサは特殊なコレクションやキャッシュ、追加ロジック付き辞書、マトリックスや多次元データ構造によく使われるよ。GetElementByIndex(5)みたいなメソッド呼び出しより、collection[5]って書けてコードが直感的で読みやすくなる。
ポイント:インデクサはクラスの性質に合ってる時だけ使おう。コレクションやデータ構造じゃないクラスならインデクサは不要。でも要素の集合を管理するクラスなら、インデクサで使いやすさがグッと上がるよ!
GO TO FULL VERSION