インデクサーの使用 (C# プログラミング ガイド)

インデクサーによって構文上の利便性がもたらされます。これを使用すると、クラス構造体、またはインターフェイスを作成でき、クライアント アプリケーションから配列と同じようにアクセスできます。 コンパイラによって、Item プロパティ (または、IndexerNameAttribute が存在する場合は別の名前が付けられたプロパティ) と、適切なアクセサー メソッドが生成されます。 インデクサーは、内部コレクションまたは配列をカプセル化することが主な目的である型で最も多く実装されます。 たとえば、24 時間のうちの異なる 10 回の時刻で記録した温度を華氏で表す TempRecord クラスがあるとします。 このクラスには、温度値を格納する float[] 型の配列 temps が含まれています。 このクラスにインデクサーを実装することで、クライアントは、float temp = tempRecord.temps[4] ではなく float temp = tempRecord[4] として TempRecord インスタンスの温度にアクセスできます。 インデクサー表記を使用すると、クライアント アプリケーションの構文が簡略化されるだけでなく、クラスとその目的が、他の開発者たちにとってわかりやすい、より直感的なものとなります。

クラスまたは構造体でインデクサーを宣言するには、次の例のように this キーワードを使用します。

// Indexer declaration
public int this[int index]
{
    // get and set accessors
}

重要

インデクサーを宣言すると、オブジェクト上に Item という名前のプロパティが自動的に生成されます。 Item プロパティは、インスタンスのメンバー アクセス式から直接アクセスすることはできません。 また、インデクサーを使用して独自の Item プロパティをオブジェクトに追加すると、CS0102 コンパイラエラーが発生します。 このエラーを回避するには、以下で説明するように、IndexerNameAttribute を使用してインデクサーの名前を変更します。

Remarks

インデクサーの型とそのパラメーターの型は、少なくとも、インデクサー自体と同程度にアクセス可能である必要があります。 アクセシビリティ レベルの詳細については、「アクセス修飾子 (C# リファレンス)」を参照してください。

インターフェイスでインデクサーを使用する方法の詳細については、「インターフェイスのインデクサー (C# プログラミング ガイド)」を参照してください。

インデクサーのシグネチャは、その仮パラメーターの数と型で構成されます。 これには、インデクサーの型や仮パラメーターの名前は含まれません。 同じクラス内に複数のインデクサーを宣言する場合は、異なるシグネチャが必要です。

インデクサーは変数として分類されません。したがって、インデクサー値は、その値が参照 (つまり、参照によって返される) でない限り、参照 (ref または out パラメーターとして) 渡すことができません。

他の言語が使用できる名前をインデクサーに指定するには、次の例のように System.Runtime.CompilerServices.IndexerNameAttribute を使用します。

// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    // get and set accessors
}

インデクサー名属性によってオーバーライドされるため、このインデクサーの名前は TheItem になります。 既定では、インデクサーの名前は Item です。

例 1

次の例は、プライベートな配列フィールド temps とインデクサーの宣言方法を示しています。 インデクサーを使用すれば、インスタンス tempRecord[i] に直接アクセスできます。 インデクサーを使用しない場合は、配列を public メンバーとして宣言し、そのメンバー tempRecord.temps[i] に直接アクセスします。

public class TempRecord
{
    // Array of temperature values
    float[] temps =
    [
        56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
        61.3F, 65.9F, 62.1F, 59.2F, 57.5F
    ];

    // To enable client code to validate input
    // when accessing your indexer.
    public int Length => temps.Length;
    
    // Indexer declaration.
    // If index is out of range, the temps array will throw the exception.
    public float this[int index]
    {
        get => temps[index];
        set => temps[index] = value;
    }
}

Console.Write ステートメントなどでインデクサーのアクセスが評価されると、get アクセサーが呼び出されることに注意してください。 したがって、get アクセサーが存在しない場合は、コンパイル時エラーが発生します。

var tempRecord = new TempRecord();

// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;

// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}

他の値を使用したインデックス作成

C# では、インデクサー パラメーター型は整数に制限されません。 たとえば、文字列をインデクサーで使用すると有効な場合があります。 このようなインデクサーは、コレクション内の文字列を検索し、適切な値を返すことによって実装される場合があります。 アクセサーはオーバーロードできるため、文字列と整数のバージョンは共存できます。

例 2

次の例では、曜日を格納するクラスを宣言しています。 get アクセサーは、曜日を示す文字列を受け取り、対応する整数を返します。 たとえば、"Sunday" は 0、"Monday" は 1 などと値を返します。

// Using a string as an indexer value
class DayCollection
{
    string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];

    // Indexer with only a get accessor with the expression-bodied definition:
    public int this[string day] => FindDayIndex(day);

    private int FindDayIndex(string day)
    {
        for (int j = 0; j < days.Length; j++)
        {
            if (days[j] == day)
            {
                return j;
            }
        }

        throw new ArgumentOutOfRangeException(
            nameof(day),
            $"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
    }
}

使用例 2

var week = new DayCollection();
Console.WriteLine(week["Fri"]);

try
{
    Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine($"Not supported input: {e.Message}");
}

例 3

次の例では、System.DayOfWeek 列挙型の使用により、曜日を格納するクラスが宣言されています。 曜日を示す DayOfWeekget アクセサーにより受け取られ、対応する整数が返されます。 たとえば、DayOfWeek.Sunday の場合は 0 が返される、DayOfWeek.Monday の場合は 1 が返されるなどです。

using Day = System.DayOfWeek;

class DayOfWeekCollection
{
    Day[] days =
    [
        Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
        Day.Thursday, Day.Friday, Day.Saturday
    ];

    // Indexer with only a get accessor with the expression-bodied definition:
    public int this[Day day] => FindDayIndex(day);

    private int FindDayIndex(Day day)
    {
        for (int j = 0; j < days.Length; j++)
        {
            if (days[j] == day)
            {
                return j;
            }
        }
        throw new ArgumentOutOfRangeException(
            nameof(day),
            $"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
    }
}

使用例 3

var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);

try
{
    Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine($"Not supported input: {e.Message}");
}

信頼性の高いプログラミング

インデクサーのセキュリティと信頼性を改善するには、主に次の 2 つの方法があります。

  • クライアント コードが無効なインデックス値を渡しても、それを処理できるように必ずエラー処理戦略を組み込んでください。 このトピックの最初の例の TempRecord クラスには Length プロパティが用意されており、入力がインデクサーに渡される前にクライアント コードで検証できるようになっています。 インデクサー自体にエラー処理コードを配置することもできます。 インデクサーのアクセサー内部でスローされる例外はすべて、ユーザーのために文書化してください。

  • get および set アクセサーのアクセシビリティを設定し、適切な制限を指定します。 これは、set アクセサーの場合、特に重要です。 詳細については、「アクセサーのアクセシビリティの制限」を参照してください。

関連項目