拡張機能のインデックス作成ツール

Note

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオン号: https://github.com/dotnet/csharplang/issues/9856

宣言

構文

拡張インデクサーは、( proposals/csharp-14.0/extensions.md を基準にして) 文法を拡張することで、拡張宣言内の許可されるメンバーのセットに追加されます。

extension_member_declaration
        : method_declaration
        | property_declaration
        | indexer_declaration // new
        | operator_declaration
        ;

通常のインデクサーと同様に、拡張インデクサーには識別子がなく、パラメーター リストによって識別されます。 拡張インデクサーは、通常のインデクサーが現在サポートしている機能の完全なセット (アクセサー本体、式形式のメンバー、参照を返すアクセサー、 scoped パラメーター、属性など) を使用できます。

インデクサーは常にインスタンス メンバーであるため、インデクサーを宣言する拡張ブロックでは、名前付きレシーバー パラメーターを指定する必要があります。

拡張メンバーに関する既存の制限は引き続き適用されます。拡張機能宣言内のインデクサーは、 abstractvirtualoverridenewsealedpartialprotected (または関連するアクセシビリティ修飾子)、または init アクセサーを指定できません。

public static class BitExtensions
{
    extension(int i)
    {
        public bool this[int index]
        {
            get => ...;
        }
    }
}

通常のインデクサーに適用される C# 標準のすべての規則は拡張インデクサーに適用されますが、拡張メンバーには暗黙的または明示的な thisはありません。

既存の拡張メンバー推論規則は引き続き適用されます。メソッド以外の拡張メンバーごとに、拡張ブロックのすべての型パラメーターを拡張機能とメンバーのパラメーターの組み合わせで使用する必要があります。

IndexerName 属性

IndexerNameAttribute 拡張インデクサーに適用できます。 属性はメタデータでは出力されませんが、その値はメンバー間の競合に影響し、メタデータ内のプロパティとアクセサーの名前を決定し、 [DefaultMemberAttribute] を出力するときに使用されます ( メタデータを参照)。

従量課金

インデクサーへのアクセス

インデクサー アクセスの規則が更新されます。インデクサー アクセスの通常の処理で該当するインデクサーが見つからない場合は、拡張機能インデクサー アクセスとしてコンストラクトを処理しようとします。

  1. 受信側の型で宣言 (または継承) されたインスタンス インデクサーを使用してバインドを試みます。 該当する候補が見つかった場合、オーバーロードの解決では、現在のインスタンス メンバーの中から選択され、停止します。
  2. 受信側の型で宣言 (または継承) された暗黙的なインスタンス インデクサーを使用してバインドを試みます。 該当する候補が見つかった場合、オーバーロードの解決では、現在のインスタンス メンバーの中から選択され、停止します。
  3. 拡張インデクサー アクセスとしてバインドを試みます。 このプロセスで該当する候補が見つかると、オーバーロードの解決によって、以下の説明に従ってそれらの拡張メンバーの中から選択され、停止します。
  4. 拡張機能の暗黙的なインデクサー アクセスとしてバインドを試みます。 このプロセスで該当する候補が見つかると、オーバーロードの解決によって、以下の説明に従ってそれらの拡張メンバーの中から選択され、停止します。

注: 要素アクセス セクションは、引数が型 dynamicを持つケースを処理するため、インデクサー アクセスとして処理されることはありません。

拡張機能インデクサー アクセス

拡張インデクサーを含む拡張メンバーは、受信側が base_access 式の場合は考慮されません。

注: 受信側が変数または値である場合、 element_access はインデクサー アクセスとしてのみ処理されるため、受信側が型の場合、拡張インデクサーは考慮されません。

element_accessE[A]を考えると、目的は拡張インデクサーを識別することです。

拡張ブロックの型パラメーターと、拡張パラメーターとインデクサーのパラメーターを組み合わせたパラメーター リストで構成される拡張シグネチャが、受信側のEと引数リストのAを組み合わせた引数リストに対して適用できる場合、拡張インデクサー候補は、受信側Eと引数リストAに適用できます。

拡張メソッドのスコープ ウォークを再利用します。拡張メソッドの呼び出しについて参照されているのと同じスコープ (現在および外側の字句スコープ、名前空間またはusing staticのインポートusing含む) を走査します。

次に、各スコープを検討します。

  • 現在のスコープ内の非ジェネリック静的クラス宣言の拡張ブロックが考慮されます。
  • これらの拡張ブロック内のインデクサーは、候補セットで構成されます。
  • アクセスできない候補は、セットから削除されます。
  • 該当しない候補 (上記で定義) は、セットから削除されます。
  • 結果の候補インデクサーのセットが空の場合は、次のスコープに進みます。または、最後のスコープに達した場合は拡張機能インデクサー アクセスの解決に失敗します (その場合は、引き続き拡張機能の暗黙的なインデクサーとして解決を試みます)。
  • それ以外の場合は、オーバーロードの解決が候補セットに適用されます。 1 つの最適なインデクサーを識別できない場合、拡張インデクサーのアクセスがあいまいになり、コンパイル時エラーが発生します。

前の手順で識別されたこの 1 つの最適なインデクサーを使用して、インデクサー アクセスは静的メソッド呼び出しとして処理されます。

使用されるコンテキストに応じて、インデクサー アクセスによって、インデクサーの get_accessor または set_accessor が呼び出されます。
インデクサー アクセスが割り当てのターゲットである場合は、 set_accessor 静的実装メソッドが呼び出され、新しい値が割り当てられます。
それ以外の場合は、 get_accessor 静的実装メソッドが呼び出され、現在の値が取得されます。
いずれの場合も、呼び出しでは、適用性チェック中に推論されたジェネリック引数と、最初の引数として受信側が使用されます。

拡張機能の暗黙的インデクサー アクセス

拡張機能の暗黙的な System.Index (または System.Range) インデクサー アクセスは、次の場合に適用できます。

  1. element_accessには、System.Index (またはSystem.Range) 型の 1 つの引数があります。
  2. 該当する Length または Count (インスタンスまたは拡張機能) プロパティが受信側の種類で見つかり、
  3. 該当する this[int] (または Slice(int, int)) (インスタンスまたは拡張機能) インデクサーが受信側の種類で見つかります。

その他の要素アクセス フォーム

要素アクセス バインディング (null 条件付き要素のアクセスまたは割り当て、オブジェクト初期化子でのインデックスの割り当て、またはリストと拡散パターン) に遅延するコンストラクトは、上記の拡張インデクサー解決に自動的に参加します。

式ツリー

拡張インデクサーは式ツリーでキャプチャできません。

XML ドキュメント

CREF 構文を使用すると、拡張インデクサーとそのアクセサーとその実装メソッドを参照できます。

Example:

/// <see cref="E.extension(int).this[string]"/>
/// <see cref="E.extension(int).get_Item(string)"/>
/// <see cref="E.extension(int).get_Item"/>
/// <see cref="E.extension(int).set_Item(string, int)"/>
/// <see cref="E.extension(int).set_Item"/>
/// <see cref="E.get_Item(int, string)"/>
/// <see cref="E.get_Item"/>
/// <see cref="E.set_Item(int, string, int)"/>
/// <see cref="E.set_Item"/>
public static class E
{
    extension(int i)
    {
        /// <summary></summary>
        public int this[string s]
        {
            get => throw null;
            set => throw null;
        }
    }
}

Metadata

拡張インデクサーは、拡張プロパティと同じ下位モデルに従います。 少なくとも 1 つのインデクサーを含む CLR レベルの拡張機能グループ化の種類ごとに、コンパイラは次を出力します。

  • throw NotImplementedException()アクセサー本体と適切な拡張マーカーの種類を参照する[ExtensionMarkerName]属性を持つItem (またはIndexerNameAttributeによって提供される値) という名前の拡張プロパティ。
  • 外側の静的クラス get_Item/set_Item という名前の実装メソッド。 これらのメソッドは、受信者パラメーターをパラメーター リストの先頭に追加し、ユーザー定義の本体を含みます。 これらは static され、拡張プロパティの実装メソッドと同じ方法でオーバーロード解決に参加します。

通常のインデクサーの動作をミラー化するために、コンパイラは、1 つ以上の拡張インデクサーを含む拡張グループ化型に対して [DefaultMemberAttribute] も出力します。 属性の MemberName は、インデクサーのメタデータ名 (既定ではItem 、または IndexerNameAttribute の値) と等しくなります。

Example

ソースコード:

static class BitExtensions
{
    extension<T>(T t)
    {
        public bool this[int index]
        {
            get => ...;
            set => ...;
        }
    }
}

出力されたメタデータ (C#に似た構文に簡略化):

[Extension]
static class BitExtensions
{
    [Extension, SpecialName, DefaultMember("Item")]
    public sealed class <G>$T0 // grouping type
    {
        [SpecialName]
        public static class <M>$T_t // marker type
        {
            [SpecialName]
            public static void <Extension>$(T t) { } // marker method
        }

        [ExtensionMarkerName("<M>$T_t")]
        public bool this[int index] // extension indexer
        {
            get => throw new NotImplementedException();
            set => throw new NotImplementedException();
        }
    }

    // accessor implementation methods
    public static bool get_Item<T>(T t, int index) => ...;
    public static void set_Item<T>(T t, int index, bool value) => ...;
}

未解決の課題

代替設計の議論を含む、未解決の問題に関連するドキュメントの一時的なセクション

対処 params

int this[int i, params string[] s] { get; set; }など、paramsを持つ拡張インデクサーがある場合は、次の 3 つの方法で使用できます。

  • 拡張機能のインデックス作成: receiver[i: 0, "Alice", "Bob"]
  • getter 実装の呼び出し: E.get_Item(receiver, i: 0, "Alice", "Bob")
  • setter 実装の呼び出し: E.set_Item(...)

しかし、セッター実装メソッドのシグネチャは何ですか?
ユーザーが使用できないメソッド シグネチャの最後のパラメーターに paramsがある場合にのみ意味があるため、 E.set_Item(... extension parameter ..., this i, params string[] s, int value)で目的を果たしません。

いくつかのオプション:

  1. setter を持つ拡張インデクサーの params を禁止する
  2. setter 実装メソッドの [ParamArray] 属性を省略する
  3. 特別な何もしない ( [ParamArray]を出力する)

私はオプション2を提案します、それは params 有用性を最大化します。 このコストは、拡張インデックス作成とあいまいさの解消構文のわずかな違いにすぎません。

決定 (LDM 2026-02-02): [ParamArray] を出力し、ツールに悪影響がないことを確認します

代入された値が型推論に与える影響

int i = 0;
i[42, null] = new object(); // fails inference
E.set_Item(i, 42, null, new object()); // infer `E.set_Item<object>`

public static class E
{
    extension<T>(int i)
    {
        public T this[int j, T t] { set { } }
    }
}
#nullable enable

int i = 0;
i[new object()] = null; // infer `E.extension<object!>` and warn on conversion of null literal to `object!`
E.set_Item(i, new object(), null); // infer `E.set_Item<object?>`

public static class E
{
    extension<T>(int i)
    {
        public T this[T t] { set { } }
    }
}

決定 (LDM 2026-02-02): インデクサーは、引数リストの受信者と引数を指定した場合にのみ推論されます (つまり、割り当てられた値は考慮されません)。

拡張 Length/Count プロパティは型をカウント可能にする必要がありますか?

暗黙的なインデックスまたは範囲インデクサーをバインドする場合、拡張機能は再生されません。

C c = new C();
_ = c[..]; // Cannot apply indexing with [] to an expression of type 'C'

class C
{
    public int Length => 0;
}

static class E
{
    public static C Slice(this C c, int i, int j) => null!;
}

ここまでの位置は、リスト パターン、コレクション式、および暗黙的なインデクサーでは、拡張プロパティを "カウント可能なプロパティ" としてカウントしないようにする必要があるということです。

要素アクセス シナリオで this[Index] または this[Range] 拡張インデクサーを公開する場合は、ターゲットの型がリスト パターンで機能することが当然です。
ただし、リスト パターンには、 Length または Count プロパティが必要です。

拡張機能のプロパティは、その要件を満たす必要がありますか? (自然に思える)

C c = new C();
var x1 = c[^1];
var x2 = c[1..];

if (c is [.., var y1]) { }
if (c is [_, .. var y2]) { }

class C { }

static class E
{
  extension(C c)
  {
    object this[System.Index] => ...;
    C this[System.Range] => ...;
    int Length => ...;
  }
}

しかし、これらのプロパティは、明示的なIndex/Rangeインデクサーがない場合に使用される暗黙的なインデクサー フォールバック (Length/Count + Slice) にも影響する必要がありますか?

C c = new C();
if (c is [var y1, .. var y2]) { }

class C
{
  C Slice(int i, int j) => ...;
}

static class E
{
  extension(C c)
  {
    object this[System.Index] => ...;
    int Length => ...;
  }
}

決定 (LDM 2026-02-02): 拡張機能は、カウント可能なプロパティや暗黙的なインデクサーのフォールバックなど、あらゆる場所に貢献する必要があります。

暗黙的なインデクサーの前または後に拡張機能インデクサー アクセスがアクセスできるかどうかを確認する

C c = new C();
_ = c[^1];

class C
{
  public int Length => ...;
  public int this[int i] => ...;
}

static class E
{
  extension(C c)
  {
    public int this[System.Index i] => ...;
  }
}

私は暗黙的なインデクサーよりも優先されるように拡張インデクサーアクセスを仕様化して実装しましたが、不要な互換性の中断を避けるために後に来る必要があると考えています。

更新プログラム (LDM 2026-02-02): 詳細な調査が必要です。 はい。拡張機能は、非拡張メンバーの後に来る必要がありますが、それ以上に、拡張機能が暗黙的なインデクサーフォールバックに貢献することを許可するという上記の決定に照らして、いくつかの具体的な提案が必要です。

決定 (LDM 2026-03-09): 順序は実インスタンス インデクサー、暗黙的なインスタンス インデクサー、次に実際の拡張インデクサー、暗黙的な拡張インデクサーです。 「https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers」を参照してください。

カウント/長さ: 名前は最初に優先されますか、拡張子と拡張子以外のどちらですか?

また、既存のフォールバックもあります。 Length はプロパティよりも優先 Count 。 拡張機能 Length 非拡張 Count プロパティの前または後に来る必要がありますか?

決定 (LDM 2026-03-09): スコープごとに (インスタンス スコープから開始し、拡張スコープを進める)、各スコープ内で最初に Length を探し、次に Countします。 「https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers」を参照してください。

暗黙的なインデクサーの提案された設計を確認する

スコープごとに、インスタンス スコープから始まり、拡張スコープに進みます。
スコープ内:

  1. 実際のインデクサーを探し、
  2. それ以外の場合は、正しい型の引数が 1 つある場合は、暗黙的なインデクサーを探します。

決定 (LDM 2026-03-09): いいえ。拡張機能のスコープに移行したら、すべての方法で拡張解決を使用します。

リスト パターンの提案されたデザインを確認する

スプレッドを含むリスト パターンについては、次の情報を探します。

  1. a Length/Count
  2. 実数または暗黙的 this[Index]
  3. 実数または暗黙的 this[Range]

それぞれを個別に探します。 しかし、暗黙的な this[Index] または this[Range]を探す場合、2 つの部分は同じスコープから取得する必要があります。

  1. Length/Count
  2. this[int]/Slice(int, int)

注: Length パターンに対する負でない処理は、型をリスト パターンで使用できる場合に開始されます (つまり、カウント可能でインデックス付け可能です)。

決定 (LDM 2026-03-09): いいえ。代わりに、この参照順序に従います。

  1. リスト パターンは、Length/Count、Indexer、Range インデクサーを個別に探すかのように解決されます
  2. インデックス インデクサーと範囲インデクサーの場合は、次の手順に従います。 インスタンス参照のみの場合は、可能な場合は "実際の" インデックスを見つけます b. インスタンス参照のみの場合は、可能な場合は暗黙的なインデクサーの部分を見つけます c. 完全検索 (インスタンス + 拡張機能) では、可能であれば "実際の" インデックスを見つけます。 完全参照 (インスタンス + 拡張機能) を使用して、可能な場合は暗黙的なインデクサーの部分を見つけます (個々の参照の各部分)

拡張 Slice 方法も貢献する必要がありますか?

_ = c[1..^1];

static class E
{
  extension(C c)
  {
    public int Length => 3;
  }
  public static C Slice(this C c, int i, int j) => ...;
}

決定 (LDM 2026-03-09): はい、従来の拡張メソッドと新しい拡張メソッドをまったく同じように扱っています。

拡張機能 Length 拡散の最適化に貢献する必要がありますか?

C c = new C();
int[] i = [0, .. c]; // Uses Length, if available, to allocate the right size

決定 (LDM 2026-03-09): いいえ。拡張機能がパフォーマンスの高い方法でこれを実装できる可能性は低いので、最適化には役立たないでしょう。

配列型または文字列型に対して暗黙的なインデクサーを使用する必要がありますか?

暗黙的なインデクサーは、形式が正しくない corlib (インスタンスの LengthGetSubArray 、または GetSubstringがない) でのみ可能であるため、これらのシナリオには影響しないことを提案します。

決定 (LDM/Mads by email 2026-04-07): 文字列または配列に拡張インデクサーがありません。

配列型または文字列型に対して拡張インデクサーを使用する必要がありますか?

配列アクセス文字列アクセスの現在の仕様規則とベースライン規則は、拡張インデクサーが配列または文字列で機能しないことを意味します。
ただし、このような拡張インデクサーの宣言は許可されています。

int[] i = ...;
_ = i[new C()];

public static class E
{
    extension(int[] i)
    {
        public int this[C c] => throw null;
    }
}
string s = "...;
_ = s[new C()];

public static class E
{
    extension(int[] i)
    {
        public int this[C c] => throw null;
    }
}

注: 拡張パラメーターはポインター型ではない可能性があるため、 ポインター要素へのアクセス に関する質問はありません。

決定 (LDM/Mads by email 2026-04-07): 文字列または配列に拡張インデクサーがありません。