次の方法で共有


拡張メンバー

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

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

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

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

宣言

構文

class_body
    : '{' class_member_declaration* '}' ';'?
    | ';'
    ;

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    | extension_declaration // add
    ;

extension_declaration // add
    : 'extension' type_parameter_list? '(' receiver_parameter ')' type_parameter_constraints_clause* extension_body
    ;

extension_body // add
    : '{' extension_member_declaration* '}' ';'?
    ;

extension_member_declaration // add
    : method_declaration
    | property_declaration
    | operator_declaration
    ;

receiver_parameter // add
    : attributes? parameter_modifiers? type identifier?
    ;

拡張宣言は、非ジェネリックで入れ子になっていない静的クラスでのみ宣言する必要があります。
extensionという名前を付ける型のエラーです。

スコーピング規則

拡張宣言の型パラメーターと受信側パラメーターは、拡張宣言の本体内のスコープ内にあります。 nameof式内を除き、静的メンバー内から受信側パラメーターを参照するのはエラーです。 拡張宣言の型パラメーターまたは受信側パラメーターと同じ名前を持つ型パラメーターまたはパラメーター (およびメンバー本体内のローカル変数とローカル関数) を宣言すると、エラーになります。

public static class E
{
    extension<T>(T[] ts)
    {
        public bool M1(T t) => ts.Contains(t);        // `T` and `ts` are in scope
        public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context
        public void M3(int T, string ts) { }          // Error: Cannot reuse names `T` and `ts`
        public void M4<T, ts>(string s) { }           // Error: Cannot reuse names `T` and `ts`
    }
}

メンバー自体が、囲む拡張宣言の型パラメーターまたは受信側パラメーターと同じ名前を持つことはエラーではありません。 メンバー名は、拡張宣言内から単純な名前検索に直接見つかりません。したがって、lookup はメンバーではなく、その名前の型パラメーターまたは受信側パラメーターを検索します。

メンバーは、外側の静的クラスで直接宣言されている静的メソッドを生み出し、単純な名前検索を使用して見つけることができます。ただし、同じ名前の拡張宣言型パラメーターまたは受信側パラメーターが最初に見つかります。

public static class E
{
    extension<T>(T[] ts)
    {
        public void T() { M(ts); } // Generated static method M<T>(T[]) is found
        public void M() { T(ts); } // Error: T is a type parameter
    }
}

拡張コンテナーとしての静的クラス

拡張機能は、現在の拡張メソッドと同様に、最上位レベルの非ジェネリック静的クラス内で宣言されているため、従来の拡張メソッドと非拡張静的メンバーと共存できます。

public static class Enumerable
{
    // New extension declaration
    extension(IEnumerable source) { ... }
    
    // Classic extension method
    public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
    
    // Non-extension member
    public static IEnumerable<int> Range(int start, int count) { ... } 
}

拡張宣言

拡張宣言は匿名であり、関連付けられている型パラメーターと制約に続いて拡張メンバー宣言のセットを 受け取る仕様 を提供します。 レシーバーの指定は、パラメーターの形式で指定することも、静的拡張メンバーのみが宣言されている場合は型にすることができます。

public static class Enumerable
{
    extension(IEnumerable source) // extension members for IEnumerable
    {
        public bool IsEmpty { get { ... } }
    }
    extension<TSource>(IEnumerable<TSource> source) // extension members for IEnumerable<TSource>
    {
        public IEnumerable<T> Where(Func<TSource, bool> predicate) { ... }
        public IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) { ... }
    }
    extension<TElement>(IEnumerable<TElement>) // static extension members for IEnumerable<TElement>
        where TElement : INumber<TElement>
    {
        public static IEnumerable<TElement> operator +(IEnumerable<TElement> first, IEnumerable<TElement> second) { ... }
    }
}

受信側仕様の型は 受信側の型 と呼ばれ、パラメーター名 (存在する場合) は 受信側パラメーターと呼ばれます。

受信側パラメーターに名前が付けられている場合、受信側の型が静的でない可能性があります。
受信者パラメーターは、名前が指定されていない場合は修飾子を持つことはできません。また、以下に示す参照修飾子のみを使用でき、それ以外の場合はscoped
受信側パラメーターには、従来の拡張メソッドの最初のパラメーターと同じ制限があります。
[EnumeratorCancellation]属性は、受信側パラメーターに配置されている場合は無視されます。

拡張メンバー

拡張メンバー宣言は、対応するインスタンスおよびクラスおよび構造体宣言の静的メンバーと構文的には同じです (コンストラクターを除く)。 インスタンス メンバーは、レシーバー パラメーター名を持つレシーバーを参照します。

public static class Enumerable
{
    extension(IEnumerable source)
    {
        // 'source' refers to receiver
        public bool IsEmpty => !source.GetEnumerator().MoveNext();
    }
}

外側の拡張宣言で受信側パラメーターが指定されていない場合、インスタンス拡張メンバーを指定するとエラーになります。

public static class Enumerable
{
    extension(IEnumerable) // No parameter name
    {
        public bool IsEmpty => true; // Error: instance extension member not allowed
    }
}

拡張機能宣言のメンバーに対して、 abstractvirtualoverridenewsealedpartial、および protected (および関連するアクセシビリティ修飾子) に対して、次の修飾子を指定するとエラーになります。
拡張宣言のメンバーに readonly 修飾子を指定するとエラーになります。
拡張宣言のプロパティには、 init アクセサーがない可能性があります。
受信側パラメーターに名前が付いていない場合、インスタンス メンバーは許可されません。

すべてのメンバーは、静的な外側のクラスの名前と拡張型の名前 (ある場合) とは異なる名前を持つ必要があります。

拡張メンバーを [ModuleInitializer] 属性で装飾するとエラーになります。

レフネス

既定では、レシーバーは、その他パラメーターと同様に、値によって、インスタンス拡張メンバーに渡されます。 ただし、パラメーター形式の拡張宣言レシーバーは、レシーバーの型が値型であることがわかっている限り、refref readonly、および in を指定できます。

NULL 許容値と属性

レシーバー型は、null 許容参照型にすることもでき、それを含めることもできます。またパラメーター形式のレシーバー指定は、属性を指定できます。タ

public static class NullableExtensions
{
    extension(string? text)
    {
        public string AsNotNull => text is null ? "" : text;
    }
    extension([NotNullWhen(false)] string? text)
    {
        public bool IsNullOrEmpty => text is null or [];
    }
    extension<T> ([NotNull] T t) where T : class?
    {
        public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t);
    }
}

従来の拡張メソッドとの互換性

インスタンス拡張メソッドは、従来の拡張メソッドによって生成されるものと一致する成果物を生成します。

具体的には、生成された静的メソッドは、宣言された拡張メソッドの属性、修飾子、名前および、拡張宣言とメソッド宣言からこの順番で、連結された型パラメーター リスト、パラメーター リスト、制約リストを保有します。

public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source) // Generate compatible extension methods
    {
        public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
        public IEnumerable<TSource> Select<TResult>(Func<TSource, TResult> selector)  { ... }
    }
}

Generates:

[Extension]
public static class Enumerable
{
    [Extension]
    public static IEnumerable<TSource> Where<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate) { ... }

    [Extension]
    public static IEnumerable<TSource> Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)  { ... }
}

オペレーター

拡張演算子には明示的なオペランド型がありますが、拡張宣言内で宣言する必要があります。

public static class Enumerable
{
    extension<TElement>(IEnumerable<TElement>) where TElement : INumber<TElement>
    {
        public static IEnumerable<TElement> operator *(IEnumerable<TElement> vector, TElement scalar) { ... }
        public static IEnumerable<TElement> operator *(TElement scalar, IEnumerable<TElement> vector) { ... }
    }
}

これにより、型パラメーターを宣言および推論でき、そのオペランド型の 1 つ内で通常のユーザー定義演算子を宣言する方法に似ています。

Checking

推論可能性: メソッド以外の拡張メンバーごとに、拡張ブロックのすべての型パラメーターを、拡張機能とメンバーのパラメーターの組み合わせで使用する必要があります。

独特: 特定の外側の静的クラス内では、同じ受信側の型 (剰余 ID 変換と型パラメーター名の置換) を持つ拡張メンバー宣言のセットは、クラスまたは構造体宣言内のメンバーに似た単一の宣言空間として扱われ、 一意性に関する同じ規則に従います。

public static class MyExtensions
{
    extension<T1>(IEnumerable<int>) // Error! T1 not inferrable
    {
        ...
    }
    extension<T2>(IEnumerable<T2>)
    {
        public bool IsEmpty { get ... }
    }
    extension<T3>(IEnumerable<T3>?)
    {
        public bool IsEmpty { get ... } // Error! Duplicate declaration
    }
}

この一意性ルールの適用には、同じ静的クラス内の従来の拡張メソッドが含まれます。 拡張宣言内のメソッドと比較するために、this パラメーターはそのレシーバー型に記載されている任意の型パラメーターと共に、レシーバー指定として扱われ、残りの型パラメーターとメソッド パラメーターは、メソッド シグネチャに使用されます。

public static class Enumerable
{
    public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
    
    extension(IEnumerable source) 
    {
        IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
    }
}

従量課金

拡張メンバーの検索が試行されると、using インポートされている静的クラス内のすべての拡張宣言が、レシーバー型に関係なく、そのメンバーを候補として提供します。 ソリューションの一環としてのみ、互換性のないレシーバー型の候補が破棄されます。
引数の型 (実際の受信者を含む) と任意の型パラメーター (拡張宣言と拡張メンバー宣言の型を組み合わせたもの) の間で、完全なジェネリック型推論が試行されます。
明示的な型引数を指定すると、拡張宣言と拡張メンバー宣言の型パラメーターに置き換えるために使用されます。

string[] strings = ...;

var query = strings.Select(s => s.Length); // extension invocation
var query2 = strings.Select<string, int>(s => s.Length); // ... with explicit full set of type arguments

var query3 = Enumerable.Select(strings, s => s.Length); // static method invocation
var query4 = Enumerable.Where<string, int>(strings, s => s.Length); // ... with explicit full set of type arguments
 
public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source)
    {
        public IEnumerable<TResult> Select<TResult>(Func<T, TResult> predicate) { ... }
    }
}

従来の拡張メソッドと同様に、出力される実装メソッドを静的に呼び出すことができます。
これにより、コンパイラは、同じ名前とアリティを持つ拡張メンバー間であいまいさを解消できます。

object.M(); // ambiguous
E1.M();

new object().M2(); // ambiguous
E1.M2(new object());

_ = _new object().P; // ambiguous
_ = E1.get_P(new object());

static class E1
{
    extension(object)
    {
        public static void M() { }
        public void M2() { }
        public int P => 42;
    }
}

static class E2
{
    extension(object)
    {
        public static void M() { }
        public void M2() { }
        public int P => 42;
    }
}

静的拡張メソッドは、インスタンス拡張メソッドと同様に解決されます (受信側の型の追加引数を検討します)。
拡張プロパティは拡張メソッドと同様に解決され、1 つのパラメーター (受信側パラメーター) と 1 つの引数 (実際の受信側の値) が使用されます。

using static ディレクティブ

using_static_directiveを使用すると、型宣言の拡張ブロックのメンバーが拡張アクセスで使用できるようになります。

using static N.E;

new object().M();
object.M2();

_ = new object().Property;
_ = object.Property2;

C c = null;
_ = c + c;
c += 1;

namespace N
{
    static class E
    {
        extension(object o)
        {
            public void M() { }
            public static void M2() { }
            public int Property => 0;
            public static int Property2 => 0;
        }

        extension(C c)
        {
            public static C operator +(C c1, C c2) => throw null;
            public void operator +=(int i) => throw null;
        }
    }
}

class C { } 

以前と同様に、指定された型の宣言に直接含まれるアクセス可能な静的メンバー (拡張メソッドを除く) を直接参照できます。
つまり、実装メソッド (拡張メソッドを除く) を静的メソッドとして直接使用できます。

using static E;

M();
System.Console.Write(get_P());
set_P(43);
_ = op_Addition(0, 0);
_ = new object() + new object();

static class E
{
    extension(object)
    {
        public static void M() { }
        public static int P { get => 42; set { } }
        public static object operator +(object o1, object o2) { return o1; }
    }
}

using_static_directiveはまだ拡張メソッドを静的メソッドとして直接インポートしないため、非静的拡張メソッドの実装メソッドを静的メソッドとして直接呼び出すことはできません。

using static E;

M(1); // error: The name 'M' does not exist in the current context

static class E
{
    extension(int i)
    {
        public void M() { }
    }
}

OverloadResolutionPriorityAttribute(オーバーロード解決優先順位属性)

外側の静的クラス内の拡張メンバーは、ORPA 値に従った優先順位付けの対象となります。 外側の静的クラスは、ORPA 規則で考慮される "包含型" と見なされます。
拡張プロパティに存在する ORPA 属性は、プロパティのアクセサーの実装メソッドにコピーされるため、これらのアクセサーがあいまいさを解消する構文を使用する場合に優先順位付けが尊重されます。

入口点

拡張ブロックのメソッドは、エントリ ポイント候補として適格ではありません(「7.1 アプリケーションの起動」を参照)。 注: 実装方法は、引き続き候補になる可能性があります。

引き下げ

拡張宣言の下位戦略は、言語レベルの決定ではありません。 ただし、言語セマンティクスを実装する以外に、特定の要件を満たす必要があります。

  • 生成された型、メンバー、およびメタデータの形式は、他のコンパイラが使用して生成できるように、すべてのケースで明確に指定する必要があります。
  • 生成されたアーティファクトは安定している必要があります。つまり、妥当な今後の変更によって、以前のバージョンに対してコンパイルされたコンシューマーが中断されないようにする必要があります。

これらの要件は、実装の進行に合わせてさらに改良が必要であり、妥当な実装アプローチを可能にするために、コーナー ケースで妥協する必要がある場合があります。

宣言のメタデータ

目標

次の設計では、次のことができます。

  • 拡張宣言シンボルのラウンドトリップをメタデータ(完全アセンブリと参照アセンブリ)を通して実行する
  • 拡張メンバーへの安定した参照 (xml ドキュメント)
  • 出力された名前をローカルで決定することは、EnC の役に立ちます。
  • パブリック API の追跡。

xml ドキュメントの場合、拡張メンバーの docID はメタデータ内の拡張メンバーの docID です。 たとえば、 cref="Extension.extension(object).M(int)" で使用される docID は M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) であり、その docID は、拡張ブロックの再コンパイルと順序付けの間で安定しています。 理想的には、拡張ブロックの制約が変更された場合にも安定したままですが、メンバーの競合に対する言語設計に悪影響を及ぼすことなく、それを実現する設計は見つかりませんでした。

EnC の場合、どこで更新された拡張メンバーがメタデータに出力されているかを、ローカルで(変更された拡張メンバーを確認するだけで)知ることができると便利です。

パブリック API 追跡の場合、より安定した名前を使用すると、ノイズが軽減されます。 ただし、技術的には、このようなシナリオでは拡張機能のグループ化の型名を使用しないでください。 拡張メンバー Mを見ると、拡張機能のグループ化の種類の名前は関係なく、重要なのは、それが属する拡張ブロックの署名です。 パブリック API 署名は、 Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) ではなく、 Extension.extension(object).M(int)と見なす必要があります。 言い換えると、拡張メンバーは、2 セットの型パラメーターと 2 セットのパラメーターを持つものとして見なされます。

概要

拡張ブロックは、CLR レベルのシグネチャによってグループ化されます。 各 CLR 等価性グループは、コンテンツ ベースの名前を持つ 拡張グループ化の種類 として出力されます。 その後、CLR 等価グループ内の拡張ブロックは、C# の等価性によってサブグループ化されます。 各 C# 等価グループは、対応する拡張グループ型にネストされた、内容に基づいた名前を持つ拡張マーカー型として出力されます。 拡張マーカーの種類には、拡張パラメーターをエンコードする 1 つの 拡張マーカー メソッド が含まれています。 拡張マーカーの種類を含む拡張マーカー メソッドは、完全な忠実性を持つ拡張ブロックのシグネチャをエンコードします。 各拡張メンバーの宣言は、適切な拡張グループ化型で出力され、属性を介してその名前によって拡張マーカー型を参照し、変更されたシグネチャを持つ最上位の静的 実装メソッド が伴います。

メタデータ エンコードのスキーマ化された概要を次に示します。

[Extension]
static class EnclosingStaticClass
{
    [Extension]
    public sealed class ExtensionGroupingType1 // has type parameters with minimal constraints sufficient to keep extension member declarations below valid
    {
        public static class ExtensionMarkerType1 // has re-declared type parameters with full fidelity of C# constraints
        {
            public static void <Extension>$(... extension parameter ...) // extension marker method
        }
        ... ExtensionMarkerType2, etc ...

        ... extension members for ExtensionGroupingType1, each points to its corresponding extension marker type ...
    }

    ... ExtensionGroupingType2, etc ...

    ... implementation methods ...
}

内包された静的クラスは、[Extension] 属性で出力されます。

CLR レベルのシグネチャと C#レベルの署名

拡張ブロックの CLR レベルのシグネチャの結果は次のとおりです。

  • 型パラメーター名を T0T1などに正規化する
  • 属性の削除
  • パラメーター名の消去
  • パラメーター修飾子 ( refinscoped、... など) を消去する
  • タプル名の消去
  • NULL 値許容注釈の消去
  • notnull制約の消去

注: その他の制約 ( new()structclassallows ref structunmanaged、型の制約など) は保持されます。

拡張機能のグループ化の種類

拡張グループ化の種類は、同じ CLR レベルのシグネチャを持つソース内の拡張ブロックの各セットのメタデータに出力されます。

  • その名前は発話できないものであり、CLR レベルのシグネチャの内容に基づいて決まります。 詳しくは、以下をご覧ください。
  • その型パラメーターには正規化された名前 (T0T1、...) があり、属性はありません。
  • これはパブリックであり、封印されています。
  • specialname フラグと[Extension]属性でマークされます。

拡張機能のグループ化の種類のコンテンツ ベースの名前は、CLR レベルのシグネチャに基づいており、次のものが含まれます。

  • 拡張パラメーターの型の完全修飾 CLR 名。
    • 参照される型パラメーター名は、 T0T1などに正規化されます。型宣言に表示される順序に基づいています。
    • 完全修飾名には、包含するアセンブリは含まれません。 型はアセンブリ間で移動するのが一般的であり、xml ドキュメント参照を壊すべきではありません。
  • 型パラメーターの制約が含まれて並べ替えられるので、ソース コードでそれらを並べ替しても名前は変更されません。 具体的には:
    • 型パラメーターの制約は宣言順に一覧表示されます。 N 番目の型パラメーターの制約は、Nth+1 型パラメーターの前に発生します。
    • 型制約は、完全な名前を序数で比較することで並べ替えられます。
    • 非型制約は決定論的に順序付けされ、型制約とのあいまいさや競合を回避するために処理されます。
  • これには属性が含まれていないため、タプル名、null 値の許容など、C#-isms は意図的に無視されます。

注: この名前は、C#-isms の再コンパイル、再順序付け、および変更 (つまり、CLR レベルのシグネチャに影響を与えない) にわたって安定した状態を維持することが保証されています。

拡張マーカーの種類

マーカー型は、拡張ブロックの C# ビューの完全な忠実性を得るために、その包含グループ化型 (拡張グループ化型) の型パラメーターを再宣言します。

拡張マーカーの種類は、同じ C# レベルのシグネチャを持つソース内の拡張ブロックの各セットのメタデータに出力されます。

  • その名前は言うに言えないものであり、拡張ブロックの C# レベルのシグネチャの内容に基づいて決定されます。 詳しくは、以下をご覧ください。
  • ソースで宣言されているグループ化型 (名前や属性を含む) に対して型パラメーターを再宣言します。
  • パブリックで静的です。
  • specialname フラグでマークされます。

拡張マーカーの種類のコンテンツ ベースの名前は、次に基づいています。

  • 型パラメーターの名前は、拡張宣言に表示される順序に含まれます
  • 型パラメーターの属性が含まれて並べ替えられるので、ソース コードでそれらを並べ替しても名前は変更されません。
  • 型パラメーターの制約が含まれて並べ替えられるので、ソース コードでそれらを並べ替しても名前は変更されません。
  • 拡張型の完全修飾 C# 名
    • これには、Null 許容注釈、タプル名などの項目が含まれます。
    • 完全修飾名には、格納されているアセンブリが含まれません。
  • 拡張パラメーターの名前
  • 決定的な順序での拡張パラメーター (refref readonlyscoped、...) の修飾子
  • 決定的な順序で拡張パラメーターに適用されるすべての属性の完全修飾名と属性引数

注: 名前は、再コンパイルと再順序付けの間で安定した状態を維持することが保証されます。
注: 拡張マーカーの種類と拡張マーカー メソッドは、参照アセンブリの一部として出力されます。

拡張マーカー メソッド

マーカー メソッドの目的は、拡張ブロックの拡張パラメーターをエンコードすることです。 拡張マーカー型のメンバーであるため、拡張マーカー型の再宣言された型パラメーターを参照できます。

各拡張マーカーの種類には、1 つのメソッドである拡張マーカー メソッドが含まれています。

  • これは静的、非ジェネリック、void を返し、 <Extension>$と呼ばれます。
  • その 1 つのパラメーターには、拡張パラメーターの属性、参照、型、および名前があります。
    拡張パラメーターで名前が指定されていない場合、パラメーター名は空です。
  • specialname フラグでマークされます。

マーカー メソッドのアクセシビリティは、対応する宣言された拡張メンバーの中で最も制限の厳しいアクセシビリティになります。 private は、何も宣言されていない場合に使用されます。

拡張メンバー

ソースの拡張ブロック内のメソッド/プロパティ宣言は、メタデータ内の拡張グループ化型のメンバーとして表されます。

  • 元のメソッドのシグネチャ (属性を含む) は保持されますが、その本文は throw NotImplementedException() に置き換えられます。
  • これらは IL で参照しないでください。
  • メソッド、プロパティ、およびアクセサーは、そのメンバーの拡張ブロックに対応する拡張マーカーの種類の名前を参照する [ExtensionMarkerName("...")] でマークされます。

実装メソッド

ソース内の拡張ブロック内のメソッド/プロパティ宣言のメソッド本体は、最上位の静的クラスの静的実装メソッドとして出力されます。

  • 実装メソッドの名前は元のメソッドと同じです。
  • 元のメソッドの型パラメーター(属性を含む)の前に、拡張ブロックから派生した型パラメーターが追加されています。
  • 元のメソッドと同じアクセシビリティと属性があります。
  • 静的メソッドを実装する場合、パラメーターと戻り値の型は同じです。
  • インスタンス メソッドを実装する場合は、元のメソッドのシグネチャの前にパラメーターが追加されます。 このパラメーターの属性、参照、型、および名前は、関連する拡張ブロックで宣言された拡張パラメーターから派生します。
  • 実装メソッドのパラメーターは、拡張ブロックの型パラメーターではなく、実装メソッドが所有する型パラメーターを参照します。
  • 元のメンバーがインスタンスの通常のメソッドである場合、実装メソッドは [Extension] 属性でマークされます。

ExtensionMarkerName 属性

ExtensionMarkerNameAttribute 型はコンパイラでのみ使用され、ソースでは使用できません。 コンパイルにまだ含まれていない場合には、型宣言はコンパイラによって合成されます。

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
public sealed class ExtensionMarkerNameAttribute : Attribute
{
    public ExtensionMarkerNameAttribute(string name)
        => Name = name;

    public string Name { get; }
}

注: 一部の属性ターゲットは将来への備えとして設けられています(拡張入れ子型、拡張フィールド、拡張イベント)。ただし、AttributeTargets.Constructor は拡張コンストラクターがコンストラクターではないため含まれていません。

注: この例では、読みやすくするために、簡略化されたコンテンツ ベースの名前を使用します。 注: C# は型パラメーターの再宣言を表すことができないため、メタデータを表すコードは有効な C# コードではありません。

メンバーを含まないグループ化のしくみを示す例を次に示します。

class E
{
    extension<T>(IEnumerable<T> source)
    {
        ... member in extension<T>(IEnumerable<T> source)
    }

    extension<U>(ref IEnumerable<U?> p)
    {
        ... member in extension<U>(ref IEnumerable<U?> p)
    }

    extension<T>(IEnumerable<U> source)
        where T : IEquatable<U>
    {
        ... member in extension<T>(IEnumerable<U> source) where T : IEquatable<U>
    }
}

は次のように出力されます。

[Extension]
class E
{
    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IEnumerable_T<T0>
    {
        [SpecialName]
        public static class <>E__ContentName1 // note: re-declares type parameter T0 as T
        {
            [SpecialName]
            public static void <Extension>$(IEnumerable<T> source) { }
        }

        [SpecialName]
        public static class <>E__ContentName2 // note: re-declares type parameter T0 as U
        {
            [SpecialName]
            public static void <Extension>$(ref IEnumerable<U?> p) { }
        }

        [ExtensionMarkerName("<>E__ContentName1")]
        ... member in extension<T>(IEnumerable<T> source)

        [ExtensionMarkerName("<>E__ContentName2")]
        ... member in extension<U>(ref IEnumerable<U?> p)
    }

    [Extension, SpecialName]
    public sealed class <>ContentName_For_IEnumerable_T_With_Constraint<T0>
       where T0 : IEquatable<T0>
    {
        [SpecialName]
        public static class <>E__ContentName3 // note: re-declares type parameter T0 as U
        {
            [SpecialName]
            public static void <Extension>$(IEnumerable<U> source) { }
        }

        [ExtensionMarkerName("ContentName3")]
        public static bool IsPresent(U value) => throw null!;
    }

    ... implementation methods
}

メンバーの出力方法を示す例を次に示します。

static class IEnumerableExtensions
{
    extension<T>(IEnumerable<T> source) where T : notnull
    {
        public void Method() { ... }
        internal static int Property { get => ...; set => ...; }
        public int Property2 { get => ...; set => ...; }
    }

    extension(IAsyncEnumerable<int> values)
    {
        public async Task<int> SumAsync() { ... }
    }

    public static void Method2() { ... }
}

は次のように出力されます。

[Extension]
static class IEnumerableExtensions
{
    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IEnumerable_T<T0>
    {
        // Extension marker type is emitted as a nested type and re-declares its type parameters to include C#-isms
        // In this example, the type parameter `T0` is re-declared as `T` with a `notnull` constraint:
        // .class <>E__IEnumerableOfT<T>.<>E__ContentName_For_IEnumerable_T_Source
        // .typeparam T
        //     .custom instance void NullableAttribute::.ctor(uint8) = (...)
        [SpecialName]
        public static class <>E__ContentName_For_IEnumerable_T_Source
        {
            [SpecialName]
            public static <Extension>$(IEnumerable<T> source) => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        public void Method() => throw null;

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        internal static int Property
        {
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            get => throw null;
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            set => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        public int Property2
        {
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            get => throw null;
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            set => throw null;
        }
    }

    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IAsyncEnumerable_Int
    {
        [SpecialName]
        public static class <>E__ContentName_For_IAsyncEnumerable_Int_Values
        {
            [SpecialName]
            public static <Extension>$(IAsyncEnumerable<int> values) => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IAsyncEnumerable_Int_Values")]
        public Task<int> SumAsync() => throw null;
    }

    // Implementation for Method
    [Extension]
    public static void Method<T>(IEnumerable<T> source) { ... }

    // Implementation for Property
    internal static int get_Property<T>() { ... }
    internal static void set_Property<T>(int value) { ... }

    // Implementation for Property2
    public static int get_Property2<T>(IEnumerable<T> source) { ... }
    public static void set_Property2<T>(IEnumerable<T> source, int value) { ... }

    // Implementation for SumAsync
    [Extension]
    public static int SumAsync(IAsyncEnumerable<int> values) { ... }

    public static void Method2() { ... }
}

拡張メンバーがソースで使用されるたびに、それらを実装メソッドへの参照として出力します。 たとえば、enumerableOfInt.Method() の呼び出しは、IEnumerableExtensions.Method<int>(enumerableOfInt)の静的呼び出しとして出力されます。

XML ドキュメント

拡張ブロックのドキュメント コメントは、マーカーの種類に対して出力されます (拡張機能ブロックの DocID は、次の例で E.<>E__MarkerContentName_For_ExtensionOfT'1 )。
<paramref><typeparamref>) を使用して拡張パラメーターと型パラメーターを参照できます。
注: 拡張メンバーに拡張パラメーターまたは型パラメーター ( <param> および <typeparam>) を文書化することはできません。

2 つの拡張ブロックが 1 つのマーカーの種類として出力される場合、そのドキュメント コメントもマージされます。

xml ドキュメントを使用するツールは、必要に応じて拡張ブロックから拡張メンバーに <param><typeparam> をコピーします (つまり、パラメーター情報はインスタンス メンバーに対してのみコピーする必要があります)。

<inheritdoc>は実装メソッドで生成され、crefを持つ関連する拡張メンバーを参照します。 たとえば、getter の実装メソッドは、拡張プロパティのドキュメントを参照します。 拡張メンバーにドキュメント コメントがない場合、 <inheritdoc> は省略されます。

拡張ブロックと拡張メンバーについては、現在、次の場合は警告しません。

  • 拡張パラメーターは文書化されていますが、拡張メンバーのパラメーターは文書化されていません
  • またはその逆
  • または、ドキュメントに記載されていない型パラメーターがある同等のシナリオの場合

たとえば、次のドキュメントコメント:

/// <summary>Summary for E</summary>
static class E
{
    /// <summary>Summary for extension block</summary>
    /// <typeparam name="T">Description for T</typeparam>
    /// <param name="t">Description for t</param>
    extension<T>(T t)
    {
        /// <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
        /// <typeparam name="U">Description for U</typeparam>
        /// <param name="u">Description for u</param>
        public void M<U>(U u) => throw null!;

        /// <summary>Summary for P</summary>
        public int P => 0;
    }
}

次の xml を生成します。

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name="T:E">
            <summary>Summary for E</summary>
        </member>
        <member name="T:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1">
            <summary>Summary for extension block</summary>
            <typeparam name="T">Description for T</typeparam>
            <param name="t">Description for t</param>
        </member>
        <member name="M:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)">
            <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
            <typeparam name="U">Description for U</typeparam>
            <param name="u">Description for u</param>
        </member>
        <member name="P:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.P">
            <summary>Summary for P</summary>
        </member>
        <member name="M:E.M``2(``0,``1)">
            <inheritdoc cref="M:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)"/>
        </member>
        <member name="M:E.get_P``1(``0)">
            <inheritdoc cref="P:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.P"/>
        </member>
    </members>
</doc>

CREFの参照

拡張ブロックは入れ子になった型のように扱うことができます。これは、シグネチャによってアドレス指定できます (1 つの拡張パラメーターを持つメソッドであるかのように)。 例: E.extension(ref int).M()

ただし、cref は拡張ブロック自体に対処できません。 E.extension(int) は、 E型の "extension" という名前のメソッドを参照できます。

static class E
{
  extension(ref int i)
  {
    void M() { } // can be addressed by cref="E.extension(ref int).M()" or cref="extension(ref int).M()" within E, but not cref="M()"
  }
  extension(ref  int i)
  {
    void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)" or cref="extension(ref int).M(int)" within E
  }
}

検索処理は、一致するすべての拡張ブロックを参照します。
拡張メンバーへの修飾されていない参照を禁止するため、cref ではそれらを禁止することもできます。

構文は次のようになります。

member_cref
  : conversion_operator_member_cref
  | extension_member_cref // added
  | indexer_member_cref
  | name_member_cref
  | operator_member_cref
  ;

extension_member_cref // added
 : 'extension' type_argument_list? cref_parameter_list '.' member_cref
 ;

qualified_cref
  : type '.' member_cref
  ;

cref
  : member_cref
  | qualified_cref
  | type_cref
  ;

最上位レベル (extension_member_cref) でextension(int).Mを使用するか、別の拡張機能 (E.extension(int).extension(string).M) で入れ子にすることはエラーです。

重大な変更

"extension" という名前を型やエイリアスに付けることはできません。

未処理の問題

未確定の構文や代替設計の説明など、未解決の問題に関連するドキュメントの一時的なセクション
  • 拡張メンバーにアクセスするときに受信者の要件を調整する必要がありますか? (コメント)
  • キーワードとして extensionextensions を確認 します (回答: extension、LDM 2025-03-24)
  • 私たちが[ModuleInitializer]を禁止したいことを確認します(回答: はい、禁止、LDM 2025-06-11)
  • 拡張ブロックをエントリ ポイント候補として破棄してもよしいことを確認 します (回答: はい、破棄、LDM 2025-06-11)
  • LangVer ロジックを確認する (新しい拡張機能をスキップする、選択した場合は考慮して報告する) (回答: 無条件でバインドし、インスタンス拡張メソッドを除く LangVer エラーを報告する、LDM 2025-06-11)
  • 拡張ブロックがマージされ、ドキュメントコメントもマージされる場合、partial は必要ですか? (回答:文書のコメントは、ブロックがマージされたときにサイレントにマージされ、 partial は必要ありません。電子メールで確認 2025-09-03)
  • メンバーに、包含型または拡張型の後に名前を付けないことを確認します。 (回答: はい、電子メールで確認 2025-09-03)

移植性の問題に照らして、グループ化/競合ルールを再検討します。 https://github.com/dotnet/roslyn/issues/79043

(回答: このシナリオは、コンテンツ ベースの型名を持つ新しいメタデータ設計の一部として解決されました。許可されています)

現在のロジックは、同じ受信者の種類を持つ拡張ブロックをグループ化することです。 これは制約を考慮していません。 これにより、このシナリオで移植性の問題が発生します。

static class E
{
   extension<T>(ref T) where T : struct
      void M()
   extension<T>(T) where T : class
      void M()
}

提案は、拡張機能グループ化タイプの設計で計画しているのと同じグループ化ロジックを使用することです。具体的には、CLR レベルの制約を考慮に入れて、notnull、タプル名、null 許容注釈を無視するということです。

refness はグループ化型名でエンコードする必要がありますか?

  • 拡張機能のグループ化の種類名に含まれない ref 提案を確認する (WG がグループ化/競合ルールを再検討した後、さらに議論する必要があります。LDM 2025-06-23) (回答: 電子メールで確認済み 2025-09-03)
public static class E
{
  extension(ref int)
  {
    public static void M()
  }
}

次のように出力されます。

public static class E
{
  public static class <>ExtensionTypeXYZ
  {
    .. marker method ...
    void M()
  }
}

また、 E.extension(ref int).M のサードパーティの CREF 参照は、 M:E.<>ExtensionGroupingTypeXYZ.M() として出力されます。 ref が削除または拡張パラメーターに追加された場合、CREF を中断させたくない可能性があります。

拡張機能としての使用方法はあいまいさのため、このシナリオはあまり気にしません。

public static class E
{
  extension(ref int)
    static void M()
  extension(int)
    static void M()
}

ただし、このシナリオは (移植性と有用性を考慮して)、競合ルールを調整した後、提案されたメタデータ設計で機能します。

static class E
{
   extension<T>(ref T) where T : struct
      void M()
   extension<T>(T) where T : class
      void M()
}

このシナリオでは移植性を失うため、refness を考慮しないことには欠点があります。

static class E
{
   extension<T>(ref T)
      void M()
   extension<T>(T)
      void M()
}
// portability issue: since we're grouping without accounting for refness, the emitted extension members conflict (not implementation members). Mitigation: keep as classic extensions or split to another static class

nameof

  • 従来の拡張メソッドや新しい拡張メソッドのように、nameof の拡張プロパティを禁止する必要がありますか? 回答: nameof(EnclosingStaticClass.ExtensionMember) を使用したいと思います。 設計が必要で、.NET 10 からパントされる場合があります。 LDM 2025-06-11)

パターンベースのコンストラクト

メソッド

  • 新しい拡張メソッドはどこで機能する必要がありますか? (回答:従来の拡張メソッドが再生されるのと同じ場所、LDM 2025-05-05)

これには次のものが含まれます。

  • GetEnumerator / GetAsyncEnumeratorforeach
  • Deconstruct の分解、位置パターン、およびforeachで使用される
  • Add コレクション初期化子内
  • GetPinnableReferencefixed
  • GetAwaiterawait

これには次の内容が含まれません。

  • Dispose / DisposeAsync usingforeach
  • MoveNext / MoveNextAsyncforeach
  • Sliceおよびintのインデクサーは、暗黙のインデクサーや場合によってはリスト パターン内で使用されます。
  • GetResultawait

プロパティとインデクサー

  • 拡張プロパティとインデクサーはどこで機能する必要がありますか? (答え: 4 つから始めましょう,LDM 2025-05-05)

次のものが含まれます。

  • オブジェクト初期化子: new C() { ExtensionProperty = ... }
  • dictionary initializer: new C() { [0] = ... }
  • with: x with { ExtensionProperty = ... }
  • プロパティ パターン: x is { ExtensionProperty: ... }

除外対象:

  • Currentforeach
  • IsCompletedawait
  • Count / Length リスト パターンのプロパティとインデクサー
  • Count / Length 暗黙的なインデクサーのプロパティとインデクサー
デリゲートを返すプロパティ
  • この図形の拡張プロパティが LINQ クエリでのみ実行され、インスタンス プロパティの実行内容と一致することを確認します。 (回答: 理にかなっています,LDM 2025-04-06)
リストと展開パターン
  • 拡張 Index/Range インデクサーがリスト パターンで再生されることを確認します (回答: C# 14 には関係ありません)
Count / Length拡張機能のプロパティが表示される場所を再検討する

コレクション式

  • 拡張機能 Add が動作します
  • 拡張 GetEnumerator はスプレッドに対して機能します
  • 拡張 GetEnumerator は要素の種類の決定に影響しません (インスタンスである必要があります)
  • 静的 Create 拡張メソッドは、祝福された 作成 メソッドとしてカウントしないでください
  • 拡張カウント可能なプロパティはコレクション式に影響しますか?

params コレクション

  • 拡張機能 Add は、params で許可される型に影響しません

辞書表現

  • 拡張インデクサーがディクショナリ式で使用されないことを確認してください。インデクサーの存在は、ディクショナリ型を定義する重要な要素の一部であるためです。 (回答: C# 14 には関係ありません)

extern

拡張機能の種類の名前付け/番号付けスキーム

問題
現在の番号付けシステムでは、 パブリック API の検証 に問題が発生し、参照のみのアセンブリと実装アセンブリの間でパブリック API が一致することを保証します。

次のいずれかの変更を行う必要がありますか? (回答: パブリック API の安定性を高めるためにコンテンツベースの名前付けスキームを採用しており、マーカーメソッドを考慮するためにツールを更新する必要があります)

  1. ツールを調整する
  2. 一部のコンテンツ ベースの名前付けスキーム (TBD) を使用する
  3. いくつかの構文を使用して名前を制御できるようにする

LINQ では、新しいジェネリック拡張 Cast メソッドが引き続き機能しない

問題
以前のロール/拡張機能の設計では、メソッドの型引数のみを明示的に指定することが可能でした。
しかし、従来の拡張メソッドからの見かけのない移行に焦点を当てているので、すべての型引数を明示的に指定する必要があります。
これにより、LINQ での拡張 Cast メソッドの使用に関する問題に対処できません。

このシナリオに対応するために拡張機能の機能を変更する必要がありますか? (答え: いいえ、これは拡張機能の解像度の設計、LDM 2025-05-05 を再検討する原因になりません)

拡張メンバーでの拡張パラメーターの制約

以下を許可する必要がありますか? (答え:いいえ、これは後で追加できます)

static class E
{
    extension<T>(T t)
    {
        public void M<U>(U u) where T : C<U>  { } // error: 'E.extension<T>(T).M<U>(U)' does not define type parameter 'T'
    }
}

public class C<T> { }

NULL 値の許容

  • 現在の設計、つまり最大の移植性/互換性を確認 します (回答: はい、LDM 2025-04-17)
    extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b)
    {
        public void AssertTrue() => throw null!;
    }
    extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i)
    {
        public void M(object? o)  => throw null!;
    }

メタデータ

  • スケルトンメソッドはNotSupportedExceptionをスローするべきか、またはその他の標準的な例外をスローするべきでしょうか (現時点ではthrow null;を使用しています)。 (回答: はい,LDM 2025-04-17)
  • メタデータのマーカー メソッドで複数のパラメーターを受け入れる必要がありますか (新しいバージョンで詳細情報が追加された場合)。 (答え: 我々は厳格なままにすることができます, LDM 2025-04-17)
  • 拡張マーカーまたは読み上げ可能な実装メソッドは、特別な名前でマークする必要がありますか? (回答: マーカーメソッドは特別な名前でマークし、それを確認する必要がありますが、実装方法ではありません。LDM 2025-04-17)
  • インスタンス拡張メソッドが内部に存在しない場合でも、静的クラスに [Extension] 属性を追加する必要がありますか? (回答: はい、LDM 2025-03-10)
  • 実装ゲッターとセッターにも [Extension] 属性を追加する必要があることを確認します。 (回答: いいえ、LDM 2025-03-10)
  • 拡張機能の種類を特別な名前でマークする必要があり、コンパイラでメタデータにこのフラグが必要であることを確認します (これはプレビューからの破壊的変更です) (回答: 承認済み、LDM 2025-06-23)

静的工場シナリオ

  • 静的メソッドの競合ルールは何ですか? (回答: 包括する静的型に対して既存の C# ルールを使用します。緩和はなし、LDM 2025-03-17)

参照

  • 読み取り可能な実装名が付いたので、インスタンス メソッドの呼び出しを解決する方法 スケルトン メソッドは、対応する実装メソッドよりも優先されます。
  • 静的拡張メソッドを解決する方法 (回答: インスタンス拡張メソッドと同様に、LDM 2025-03-03)
  • プロパティを解決する方法 (おおまかに回答 LDM 2025-03-03, しかし、改善のためのフォローアップが必要)
  • 拡張パラメーターと型パラメーターのスコープとシャドウルール (回答: 拡張ブロックのスコープ内、シャドウ禁止、LDM 2025-03-10)
  • 新しい拡張メソッドに ORPA を適用する方法 (回答: 拡張ブロックは透過的とし、ORPA の「包含型」は外側の静的クラスである LDM 2025-04-17)
public static class Extensions
{
    extension(Type1)
    {
        [OverloadResolutionPriority(1)]
        public void Overload(...)
    }
    extension(Type2)
    {
        public void Overload(...)
    }
}
  • 新しい拡張プロパティに ORPA を適用する必要がありますか? (回答: はい、ORPA は実装方法にコピーする必要があります。LDM 2025-04-23)
public static class Extensions
{
    extension(int[] i)
    {
        public P { get => }
    }
    extension(ReadOnlySpan<int> r)
    {
       [OverloadResolutionPriority(1)]
       public P { get => }
    }
}
  • 従来の拡張機能の解決規則を再調整する方法 どうしますか
    1. クラシック拡張メソッドの標準を更新し、これを使用して新しい拡張メソッドを記述します。
    2. 従来の拡張メソッドの既存の言語を保持し、これを使用して新しい拡張メソッドも記述しますが、両方に既知の仕様の偏差があります。
    3. 従来の拡張メソッドの既存の言語を保持しますが、新しい拡張メソッドには異なる言語を使用し、クラシック拡張メソッドの既知の仕様の偏差しかない場合は、
  • プロパティ アクセスで明示的な型引数を禁止することを確認 します (回答: WG で説明されている明示的な型引数を持つプロパティ アクセスなし)
string s = "ran";
_ = s.P<object>; // error

static class E
{
    extension<T>(T t)
    {
        public int P => 0;
    }
}
  • 受信側が型の場合でも、より良いルールを適用することを確認 します (回答: 静的拡張メンバーを解決するときに型のみの拡張パラメーターを考慮する必要があります。LDM 2025-06-23)
int.M();

static class E1
{
    extension(int)
    {
        public static void M() { }
    }
}
static class E2
{
    extension(in int i)
    {
        public static void M() => throw null;
    }
}
  • メソッドとプロパティの両方が適用可能な場合にあいまいさを持つことに問題があることを確認 します (回答: .NET 10、LDM 2025-06-23 からパントアウトして、現状よりも優れた方法を提案するように設計する必要があります)
  • すべてのメンバーに対して何らかの改善が必要ないことを確認してから、最終的なメンバーの種類を決定しましょう (回答: .NET 10 への導入は見送り、WG 2025-07-02)
string s = null;
s.M(); // error

static class E
{
    extension(string s)
    {
        public System.Action M => throw null;
    }
    extension(object o)
    {
        public string M() => throw null;
    }
}
  • 拡張宣言内に暗黙的なレシーバーがありますか? (答え:いいえ、LDMで以前に議論されました)
static class E
{
    extension(object o)
    {
        public void M() 
        {
            M2();
        }
        public void M2() { }
    }
}
  • 型パラメーターの検索を許可する必要がありますか? (ディスカッション)(回答: いいえ、フィードバックをお待ちください。LDM 2025-04-16)

アクセシビリティ

  • 拡張機能宣言内のアクセシビリティの意味は何ですか? (回答: 拡張宣言はアクセシビリティ スコープとしてカウントされません。LDM 2025-03-17)
  • 静的メンバーの場合でも、受信側パラメーターに "一貫性のないアクセシビリティ" チェックを適用する必要がありますか? (回答: はい,LDM 2025-04-17)
public static class Extensions
{
    extension(PrivateType p)
    {
        // We report inconsistent accessibility error, 
        //   because we generate a `public static void M(PrivateType p)` implementation in enclosing type
        public void M() { } 

        public static void M2() { } // should we also report here, even though not technically necessary?
    }

    private class PrivateType { }
}

拡張機能宣言の検証

  • メソッドしかない型パラメーターの検証 (推論可能性: すべての型パラメーターを拡張パラメーターの型に含める必要があります) を緩和する必要がありますか? (回答: はい、LDM 2025-04-06)これにより、100% のクラシック拡張メソッドを移植できます。
    TResult M<TResult, TSource>(this TSource source)がある場合は、extension<TResult, TSource>(TSource source) { TResult M() ... }として移植できます。

  • 拡張機能で init 専用アクセサーを許可する必要があるかどうかを確認 します (回答: 今のところ禁止しても問題ありません。LDM 2025-04-17)

  • 受信側の ref-ness の唯一の違いは、 extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }許可する必要がありますか? (回答: いいえ、仕様に関する規則を保持する、LDM 2025-03-24)

  • このような extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }のような競合について不平を言うべきですか? (回答: はい、仕様に関する規則を保持します。LDM 2025-03-24)

  • 実装メソッド間の競合ではないスケルトン メソッド間の競合について不平を言うべきですか? (回答: はい、仕様に関する規則を保持します。LDM 2025-03-24)

static class E
{
    extension(object)
    {
        public void Method() {  }
        public static void Method() { }
    }
}

現在の競合ルールは 1 です。 クラス/構造体ルールを使用して、類似の拡張機能内で競合がないことを確認します。2。 さまざまな拡張機能の宣言間で実装メソッド間の競合がないことを確認します。

  • ルールの最初の部分がまだ必要でしょうか? (回答: はい、API の使用に役立つこの構造を維持しています。LDM 2025-03-24)

XML ドキュメント

  • 拡張メンバーで receiver パラメーターへの paramref はサポートされていますか? 静的な場合でも? 出力でエンコードされる方法 おそらく標準的な方法<paramref name="..."/>は人間には問題なく使用できるでしょうが、APIのパラメーターに存在しないことで一部の既存ツールが問題を起こすリスクがあります。 (回答: 拡張メンバー、LDM 2025-05-05 では拡張パラメーターへの yes paramref が許可されます)
  • スピーキング可能な名前を持つ実装メソッドにドキュメントコメントをコピーすることになっていますか? (回答: コピーなし、LDM 2025-05-05)
  • インスタンス メソッド <param> 拡張コンテナーから receiver パラメーターに対応する要素をコピーする必要がありますか? コンテナーから実装メソッド (<typeparam> など) に他の何かをコピーする必要がありますか? (回答: コピーなし、LDM 2025-05-05)
  • 拡張メンバーでオーバーライドとして拡張パラメーターの <param> を許可する必要がありますか? (回答: いいえ、現時点では LDM 2025-05-05)
  • 拡張ブロックの概要はどこに表示されますか?

CREF

  • 構文を確認 する (回答: 提案が良い,LDM 2025-06-09)
  • 拡張ブロック (E.extension(int)) を参照できますか? (回答: いいえ、LDM 2025-06-09)
  • 修飾されていない構文 ( extension(int).Member) を使用してメンバーを参照できる必要がありますか? (回答: はい、LDM 2025-06-09)
  • XML エスケープを回避するために、話せない名前に異なる文字を使用する必要がありますか? (回答: WG、LDMの判断に委ねる、2025-06-09)
  • スケルトンメソッドと実装メソッドの両方への参照が可能であることを確認します( E.ME.extension(int).M)。どちらも必要と思われます (拡張プロパティと従来の拡張メソッドの移植性)。 (回答: はい、LDM 2025-06-09)
  • 拡張機能メタデータ名はバージョン管理ドキュメントで問題がありますか? (回答: はい、序数から離れ、コンテンツベースの安定した名前付けスキームを使用します)

メンバーの種類を増やすサポートを追加する

この設計をすべて一度に実装する必要はありませんが、一度に 1 つまたは数種類のメンバーにアプローチできます。 コア ライブラリの既知のシナリオに基づいて、次の順序で作業する必要があります。

  1. プロパティとメソッド (インスタンスと静的)
  2. オペレーター
  3. インデクサー (インスタンスと静的、以前の時点で日和見的に実行される場合があります)
  4. ほかに何か

他の種類のメンバーに対してどれくらいデザインをフロントロードしますか?

extension_member_declaration // add
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

入れ子になった型

拡張の入れ子になった型を使用して先に進む場合は、前のディスカッションの注意事項を以下に示すので参照してください。

  • 2 つの拡張宣言が、同じ名前とアリティを持つ入れ子になった拡張型を宣言した場合、競合が発生します。 メタデータでこれを表すソリューションはありません。
  • メタデータについて説明した大まかなアプローチ:
    1. 元の型パラメーターを持ち、メンバーなしでスケルトンの入れ子になった型を出力します
    2. 拡張宣言から先頭に付加された型パラメーターを持つ実装のネストされた型と、ソースに記述されたままのすべてのメンバー実装を出力するでしょう (型パラメーターへの参照を除く)

コンストラクター

コンストラクター本文は、this キーワードを使用して新しく作成された値にアクセスできるため、コンストラクターは、一般的に C# ではインスタンス メンバーとして記述されます。 これは、パラメーターとして渡す前の値がないため、インスタンス拡張メンバーに対するパラメーターベースのアプローチではうまく機能しません。

代わりに、拡張コンストラクターは静的ファクトリ メソッドと同様に動作します。 これらは、レシーバー パラメーター名に依存しないという意味で静的メンバーと見なされます。 その本文は、構築結果を明示的に作成して返す必要があります。 メンバー自体はコンストラクター構文でまだ宣言されていますが、this 初期化子または base 初期化子を持つことができず、アクセス可能なコンストラクターを持つレシーバー型には依存しません。

これは、インターフェイスや列挙型など、独自のコンストラクターがない型に対して拡張コンストラクターを宣言できることを意味します。

public static class Enumerable
{
    extension(IEnumerable<int>)
    {
        public static IEnumerable(int start, int count) => Range(start, count);
    }
    public static IEnumerable<int> Range(int start, int count) { ... } 
}

Allows:

var range = new IEnumerable<int>(1, 100);

短いフォーム

提案された設計では、レシーバー仕様の各メンバーへの繰り返しを回避できますが、最終的に拡張メンバーは静的クラス 拡張宣言で2層にネストされます。 静的クラスには 1 つの拡張宣言しか含まれず、拡張宣言にはメンバーが 1 つだけ含まれるのが一般的であり、そのようなケースの構文の省略形を許可することは妥当性が高いと思われます。

静的クラスと拡張宣言をマージします。

public static class EmptyExtensions : extension(IEnumerable source)
{
    public bool IsEmpty => !source.GetEnumerator().MoveNext();
}

これは、拡張メンバーのコンテナー自体に名前が付けられた "型ベース" アプローチと呼ばれるように見えます。

拡張宣言と拡張メンバーのマージ:

public static class Bits
{
    extension(ref ulong bits) public bool this[int index]
    {
        get => (bits & Mask(index)) != 0;
        set => bits = value ? bits | Mask(index) : bits & ~Mask(index);
    }
    static ulong Mask(int index) => 1ul << index;
}
 
public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source) public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
}

これは、各拡張メンバーに独自のレシーバー仕様が含まれている、"メンバーベース" のアプローチを呼び出してきたもののように見えます。