注
この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の 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
}
}
拡張機能宣言のメンバーに対して、 abstract
、 virtual
、 override
、 new
、 sealed
、 partial
、および protected
(および関連するアクセシビリティ修飾子) に対して、次の修飾子を指定するとエラーになります。
拡張宣言のプロパティには、 init
アクセサーがない可能性があります。
受信側パラメーターに名前が付いていない場合、インスタンス メンバーは許可されません。
拡張メンバーを [ModuleInitializer]
属性で装飾するとエラーになります。
レフネス
既定では、受信側は、他のパラメーターと同様に、値によってインスタンス拡張メンバーに渡されます。
ただし、パラメーター形式の拡張宣言レシーバーは、受信側の型が値型であることがわかっている限り、 ref
、 ref readonly
、および in
を指定できます。
ref
を指定すると、インスタンス メンバーまたはそのアクセサーの 1 つをreadonly
宣言できるため、受信側を変更できなくなります。
public static class Bits
{
extension(ref ulong bits) // receiver is passed by ref
{
public bool this[int index]
{
set => bits = value ? bits | Mask(index) : bits & ~Mask(index); // mutates receiver
readonly get => (bits & Mask(index)) != 0; // cannot mutate receiver
}
}
static ulong Mask(int index) => 1ul << index;
}
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) { ... }
}
}
生成:
[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 つ内で通常のユーザー定義演算子を宣言する方法に似ています。
確認しています
推論可能性: メソッド以外の拡張メンバーごとに、拡張ブロックのすべての型パラメーターを、拡張機能とメンバーの組み合わせたパラメーター セットで使用する必要があります。
独特: 特定の外側の静的クラス内では、同じ受信側の型 (剰余 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 つの引数 (実際の受信側の値) が使用されます。
OverloadResolutionPriorityAttribute(オーバーロード解決優先順位属性)
外側の静的クラス内の拡張メンバーは、ORPA 値に従った優先順位付けの対象となります。 外側の静的クラスは、ORPA 規則で考慮される "包含型" と見なされます。
拡張プロパティに存在するすべての ORPA 属性は、プロパティのアクセサーの実装メソッドにコピーされるため、これらのアクセサーがあいまいさの構文を使用する場合は優先順位付けが考慮されます。
入口点
拡張ブロックのメソッドは、エントリ ポイント候補として適格ではありません(「7.1 アプリケーションの起動」を参照)。 注: 実装方法は、引き続き候補になる可能性があります。
引き下げ
拡張宣言の下げ戦略は、言語レベルの決定ではありません。 ただし、言語セマンティクスを実装する以外に、特定の要件を満たす必要があります。
- 生成される型、メンバー、およびメタデータの形式は、他のコンパイラが使用して生成できるように、すべてのケースで明確に指定する必要があります。
- 生成される成果物は安定している必要があります。つまり、妥当な後の変更によって、以前のバージョンに対してコンパイルされたコンシューマーが中断されないようにする必要があります。
これらの要件は、実装の進行に合わせてさらに改良が必要であり、妥当な実装アプローチを可能にするために、コーナー ケースで侵害される可能性があります。
宣言のメタデータ
各拡張宣言は、マーカーメソッドとスケルトンメンバーを備えたスケルトン型として出力されます。
各スケルトン メンバーには、シグネチャが変更された最上位の静的実装メソッドが付属しています。
拡張宣言の包含静的クラスは、 [Extension]
属性でマークされます。
スケルトン
ソース内の各拡張宣言は、メタデータ内の拡張宣言として出力されます。
- その名前は、プログラム内の字句の順序に基づいて決まります。
この名前は、再コンパイルの間も安定しているとは限りません。 以下では、<>E__
の後にインデックスを使用します。 たとえば、<>E__2
と指定します。 - その型パラメーターは、ソース (属性を含む) で宣言されたパラメーターです。
- そのアクセシビリティはパブリックです。
-
specialname
フラグでマークされます。
ソースの拡張宣言のメソッド/プロパティ宣言は、メタデータのスケルトン メンバーとして表されます。
元のメソッドのシグネチャ (属性を含む) は保持されますが、その本体は throw null
に置き換えられます。
これらは IL で参照しないでください。
注: これは ref アセンブリに似ています。
throw null
本文を使用する理由は、(本文がないのではなく) IL 検証を実行して合格できるようにするためです (したがって、メタデータの完全性を検証します)。
拡張マーカー メソッドは、受信側パラメーターをエンコードします。
- これはプライベートで静的であり、
<Extension>$
と呼ばれます。 - 拡張宣言の受信側パラメーターには、属性、参照特性、型、および名前があります。
- 受信側パラメーターで名前が指定されていない場合、パラメーター名は空です。
注: これにより、メタデータ (完全アセンブリと参照アセンブリ) を介した拡張宣言シンボルのラウンドトリップが可能になります。
注: ソースで重複する拡張宣言が見つかった場合は、メタデータに 1 つの拡張スケルトン型のみを出力することを選択できます。
実装
ソース内の拡張宣言内のメソッド/プロパティ宣言のメソッド本体は、最上位の静的クラスの静的実装メソッドとして出力されます。
- 実装メソッドの名前は元のメソッドと同じです。
- これには、元のメソッドの型パラメーター (属性を含む) の前に拡張宣言から派生した型パラメーターが含まれています。
- 元のメソッドと同じアクセシビリティと属性があります。
- 静的メソッドを実装する場合、パラメーターと戻り値の型は同じです。
- インスタンス メソッドを実装する場合は、元のメソッドのシグネチャの前にパラメーターが追加されます。 このパラメーターの属性、参照、型、および名前は、関連する拡張宣言で宣言された受信側パラメーターから派生します。
- 実装メソッドのパラメーターは、拡張宣言のパラメーターではなく、実装メソッドが所有する型パラメーターを参照します。
- 元のメンバーがインスタンスの通常のメソッドである場合、実装メソッドは
[Extension]
属性でマークされます。
例えば次が挙げられます。
static class IEnumerableExtensions
{
extension<T>(IEnumerable<T> source)
{
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
{
public class <>E__1<T>
{
private static <Extension>$(IEnumerable<T> source) => throw null;
public void Method() => throw null;
internal static int Property { get => throw null; set => throw null; }
public int Property2 { get => throw null; set => throw null; }
}
public class <>E__2
{
private static <Extension>$(IAsyncEnumerable<int> values) => throw null;
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__0'1
)。
拡張メンバーに関するドキュメントコメントは、スケルトンメンバーに対して出力されます。
<paramref>
と<typeparamref>
) を使用して拡張パラメーターと型パラメーターを参照できます。
注: 拡張メンバーに拡張パラメーターまたは型パラメーター ( <param>
および <typeparam>
) を文書化することはできません。
xml ドキュメントを使用するツールは、必要に応じて拡張ブロックから拡張メンバーに <param>
と <typeparam>
をコピーします (つまり、パラメーター情報はインスタンス メンバーに対してのみコピーする必要があります)。
<inheritdoc>
は実装メソッドで生成され、cref
を持つ関連するスケルトン メンバーを参照します。 たとえば、ゲッターの実装メソッドは、スケルトン プロパティのドキュメントを参照します。
スケルトン メンバーにドキュメント コメントがない場合、 <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.<>E__0`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.<>E__0`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.<>E__0`1.P">
<summary>Summary for P</summary>
</member>
<member name="M:E.M``2(``0,``1)">
<inheritdoc cref="M:E.<>E__0`1.M``1(``0)"/>
</member>
<member name="M:E.get_P``1(``0)">
<inheritdoc cref="P:E.<>E__0`1.P"/>
</member>
</members>
</doc>
CREFの参照
拡張ブロックは入れ子になった型のように扱うことができます。これは、シグネチャによってアドレス指定できます (1 つの拡張パラメーターを持つメソッドであるかのように)。
例: E.extension(ref int).M()
。
static class E
{
extension(ref int i)
{
void M() { } // can be addressed by cref="E.extension(ref int).M()"
}
extension(ref int i)
{
void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)"
}
}
検索処理は、一致するすべての拡張ブロックを参照します。
拡張メンバーへの修飾されていない参照を禁止するため、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
) で入れ子にすることはエラーです。
注: E.extension(int)
は、 E
型の "extension" という名前のメソッドを参照するため、拡張ブロックへの cref は許可されません。
重大な変更
"extension" という名前を型やエイリアスに付けることはできません。
Issue のオープン
-
キーワードとしてします (回答:extension
とextensions
を確認extension
、LDM 2025-03-24) -
[ModuleInitializer]
を禁止することを確認 -
拡張ブロックをエントリ ポイント候補として破棄してもよしいことを確認します (回答: はい、破棄、LDM 2025-06-11) -
LangVer ロジックを確認する (新しい拡張機能をスキップする、または選択されたときにそれらを検討して報告する)(回答: 無条件でバインドし、インスタンス拡張メソッドを除く LangVer エラーを報告する、LDM 2025-06-11) - 拡張メンバーにアクセスするときに受信者の要件を調整する必要がありますか? (コメント)たとえば、
new Struct() { Property = 42 }
。
nameof
従来の拡張メソッドや新しい拡張メソッドのように、nameof の拡張プロパティを禁止する必要がありますか?(回答: 'nameof(EnclosingStaticClass.ExtensionMember) を使用します。 .NET 10 からパントされる可能性が高い設計が必要です。 LDM 2025-06-11)
パターンベースのコンストラクト
メソッド
新しい拡張メソッドはどこで機能する必要がありますか?(回答: クラシック拡張メソッドが利用されるのと同じ場所、LDM 2025-05-05)これに含まれるものは次の通りです。-
GetEnumerator
/GetAsyncEnumerator
でforeach
-
Deconstruct
の分解、位置パターン、およびforeachで使用される -
Add
コレクション初期化子内 -
GetPinnableReference
のfixed
-
GetAwaiter
のawait
これには次の内容が含まれません。
-
Dispose
/DisposeAsync
using
とforeach
-
MoveNext
/MoveNextAsync
でforeach
-
Slice
およびint
のインデクサーは、暗黙のインデクサーや場合によってはリスト パターン内で使用されます。 -
GetResult
のawait
プロパティとインデクサー
拡張プロパティとインデクサーはどこで機能する必要がありますか?(答え: 4 つから始めましょう,LDM 2025-05-05)
次のものが含まれます。
- オブジェクト初期化子:
new C() { ExtensionProperty = ... }
- dictionary initializer:
new C() { [0] = ... }
-
with
:x with { ExtensionProperty = ... }
- プロパティ パターン:
x is { ExtensionProperty: ... }
除外対象:
-
Current
のforeach
-
IsCompleted
のawait
-
Count
/Length
リスト パターンのプロパティとインデクサー -
Count
/Length
暗黙的なインデクサーのプロパティとインデクサー
デリゲートを返すプロパティ
この図形の拡張プロパティが LINQ クエリでのみ実行され、インスタンス プロパティの実行内容と一致することを確認します。(回答: 理にかなっています,LDM 2025-04-06)
リストと展開パターン
- 拡張機能
Index
/Range
インデクサーがリスト パターンで機能すべきかを確認する
Count
/
Length
拡張機能のプロパティが表示される場所を再検討する
コレクション式
- 拡張機能
Add
が動作します - 拡張
GetEnumerator
はスプレッドに対して機能します - 拡張
GetEnumerator
は要素の種類の決定に影響しません (インスタンスである必要があります) - 静的
Create
拡張メソッドは、祝福された 作成 メソッドとしてカウントしないでください - 拡張カウント可能なプロパティはコレクション式に影響しますか?
params
コレクション
- 拡張機能
Add
は、params
で許可される型に影響しません
辞書表現
- 拡張インデクサーがディクショナリ式で使用されないことを確認してください。インデクサーの存在は、ディクショナリ型を定義する重要な要素の一部であるためです。
extern
- 移植性のために
extern
を許可する予定です。 https://github.com/dotnet/roslyn/issues/78572
スケルトン型の名前付け/番号付けスキーム
問題
現在の番号付けシステムでは、 パブリック API の検証 に問題が発生し、参照のみのアセンブリと実装アセンブリの間でパブリック API が一致することを保証します。
次のいずれかの変更を行う必要がありますか? (回答:ツールを調整し、番号付けの実装を調整します,LDM 2025-05-05)
- ツールを調整する
- 一部のコンテンツ ベースの名前付けスキーム (TBD) を使用する
- いくつかの構文を使用して名前を制御できるようにする
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!;
}
メタデータ
スケルトンメソッドは(回答: はい,LDM 2025-04-17)NotSupportedException
をスローするべきか、またはその他の標準的な例外をスローするべきでしょうか (現時点ではthrow null;
を使用しています)。メタデータのマーカー メソッドで複数のパラメーターを受け入れる必要がありますか (新しいバージョンで詳細情報が追加された場合)。(答え: 我々は厳格なままにすることができます, LDM 2025-04-17)拡張マーカーまたは読み上げ可能な実装メソッドは、特別な名前でマークする必要がありますか?(回答: マーカーメソッドは特別な名前でマークし、それを確認する必要がありますが、実装方法ではありません。LDM 2025-04-17)インスタンス拡張メソッドが内部に存在しない場合でも、静的クラスに(回答: はい、LDM 2025-03-10)[Extension]
属性を追加する必要がありますか?実装ゲッターとセッターにも(回答: いいえ、LDM 2025-03-10)[Extension]
属性を追加する必要があることを確認します。- スケルトン型に特別な名前を付ける必要があり、コンパイラでメタデータにこのフラグが必要であることを確認します (これはプレビューからの破壊的変更です)
静的工場シナリオ
静的メソッドの競合ルールは何ですか?(回答: 包括する静的型に対して既存の 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 => }
}
}
- 従来の拡張機能の解決規則を再調整する方法 どうしますか
- クラシック拡張メソッドの標準を更新し、これを使用して新しい拡張メソッドを記述します。
- 従来の拡張メソッドの既存の言語を保持し、これを使用して新しい拡張メソッドも記述しますが、両方に既知の仕様の偏差があります。
- 従来の拡張メソッドの既存の言語を保持しますが、新しい拡張メソッドには異なる言語を使用し、クラシック拡張メソッドの既知の仕様の偏差しかない場合は、
-
プロパティ アクセスで明示的な型引数を禁止することを確認します (回答: WG で説明されている明示的な型引数を持つプロパティ アクセスなし)
string s = "ran";
_ = s.P<object>; // error
static class E
{
extension<T>(T t)
{
public int P => 0;
}
}
- 受信側が型の場合でも、より良いルールを適用することを確認します
int.M();
static class E1
{
extension(int)
{
public static void M() { }
}
}
static class E2
{
extension(in int i)
{
public static void M() => throw null;
}
}
- 優勝メンバーの種類を決定する前に、すべてのメンバーに対していくつかの優位性が求められていないことを確認します。
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 の唯一の違いは、(回答: いいえ、仕様に関する規則を保持する、LDM 2025-03-24)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)
static class E
{
extension(object)
{
public void Method() { }
public static void Method() { }
}
}
現在の競合ルールは 1 です。 クラス/構造体ルールを使用して、類似の拡張機能内で競合がないことを確認します。2。 さまざまな拡張機能の宣言間で実装メソッド間の競合がないことを確認します。
ルールの最初の部分がまだ必要でしょうか?(回答: はい、API の使用に役立つこの構造を維持しています。LDM 2025-03-24)
XML ドキュメント
拡張メンバーで receiver パラメーターへの(回答: 拡張メンバー、LDM 2025-05-05 では拡張パラメーターへの yes paramref が許可されます)paramref
はサポートされていますか? 静的な場合でも? 出力でエンコードされる方法 おそらく標準的な方法<paramref name="..."/>
は人間には問題なく使用できるでしょうが、APIのパラメーターに存在しないことで一部の既存ツールが問題を起こすリスクがあります。スピーキング可能な名前を持つ実装メソッドにドキュメントコメントをコピーすることになっていますか?(回答: コピーなし、LDM 2025-05-05)インスタンス メソッド(回答: コピーなし、LDM 2025-05-05)<param>
拡張コンテナーから receiver パラメーターに対応する要素をコピーする必要がありますか? コンテナーから実装メソッド (<typeparam>
など) に他の何かをコピーする必要がありますか?拡張メンバーでオーバーライドとして拡張パラメーターの(回答: いいえ、現時点では LDM 2025-05-05)<param>
を許可する必要がありますか?- 拡張ブロックの概要はどこに表示されますか?
CREF
-
構文を確認する (回答: 提案が良い,LDM 2025-06-09) 拡張ブロック ((回答: いいえ、LDM 2025-06-09)E.extension(int)
) を参照できますか?修飾されていない構文 ((回答: はい、LDM 2025-06-09)extension(int).Member
) を使用してメンバーを参照できる必要がありますか?- XML エスケープを回避するために、話せない名前に異なる文字を使用する必要がありますか? (回答: WG、LDMの判断に委ねる、2025-06-09)
スケルトンメソッドと実装メソッドの両方への参照が可能であることを確認します((回答: はい、LDM 2025-06-09)E.M
とE.extension(int).M
)。どちらも必要と思われます (拡張プロパティと従来の拡張メソッドの移植性)。- 拡張機能メタデータ名はバージョン管理ドキュメントで問題がありますか?
メンバーの種類を増やすサポートを追加する
この設計をすべて一度に実装する必要はありませんが、一度に 1 つまたは数種類のメンバーにアプローチできます。 コア ライブラリの既知のシナリオに基づいて、次の順序で作業する必要があります。
- プロパティとメソッド (インスタンスと静的)
- オペレーター
- インデクサー (インスタンスと静的、以前の時点で日和見的に実行される場合があります)
- その他
他の種類のメンバーに対してデザインをフロントロードするには、どのくらいの量が必要ですか?
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 つの拡張宣言が、同じ名前とアリティを持つ入れ子になった拡張型を宣言した場合、競合が発生します。 メタデータでこれを表すソリューションはありません。
- メタデータについて説明した大まかなアプローチ:
- 元の型パラメーターを持ち、メンバーなしでスケルトンの入れ子になった型を出力します
- 拡張宣言から先頭に付加された型パラメーターを持つ実装のネストされた型と、ソースに記述されたままのすべてのメンバー実装を出力するでしょう (型パラメーターへの参照を除く)
コンストラクター
コンストラクターは、 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) { ... }
}
により:
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) { ... }
}
これは、各拡張メンバーに独自のレシーバー仕様が含まれている、"メンバーベース" のアプローチを呼び出してきたもののように見えます。
C# feature specifications