23.1 全般
C# 言語の多くを使用すると、プログラマーはプログラムで定義されているエンティティに関する宣言型情報を指定できます。 たとえば、クラス内のメソッドのアクセシビリティは、method_modifierpublic、protected、internal および private で修飾することによって指定されます。
C# を使用すると、プログラマは 属性s と呼ばれる新しい種類の宣言型情報を発明できます。 プログラマーは、さまざまなプログラム エンティティに属性をアタッチし、実行時の環境で属性情報を取得できます。
注: たとえば、フレームワークでは、特定のプログラム要素 (クラスやメソッドなど) に配置できる
HelpAttribute属性を定義すると、それらのプログラム要素からドキュメントへのマッピングを提供できます。 注釈
属性は、位置指定パラメーターと名前付きパラメーター (§23.2.3) を持つ属性クラス (§23.2) の宣言によって定義されます。 属性は、属性仕様 (§23.3) を使用して C# プログラムのエンティティにアタッチされ、実行時に属性インスタンス (§23.4) として取得できます。
23.2 属性クラス
23.2.1 全般
抽象クラス System.Attribute から派生するクラスは、直接的または間接的に関係なく、属性クラスです。 属性クラスの宣言は、プログラム エンティティに配置できる新しい種類の属性を定義します。 慣例により、属性クラスには Attribute のサフィックスを付けた名前が付けられます。 属性の使用には、このサフィックスを含められますが、省略もできます。
ジェネリック クラス宣言では、直接または間接基底クラスとして System.Attribute を使用しないでください。
例:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute終了サンプル
23.2.2 属性の使用法
属性 AttributeUsage (§23.5.2) は、属性クラスの使用方法を記述するために使用されます。
AttributeUsage には位置パラメーター (§23.2.3) があり、属性クラスで使用できるプログラム エンティティの種類を指定できます。
例: 次の例では、
SimpleAttributeと interface_declaration のみに配置できる という名前の属性クラスを定義し、Simple属性の使用方法について説明します。[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}この属性は名前
SimpleAttributeで定義されていますが、この属性を使用する場合は、Attributeサフィックスが省略され、短い名前Simpleになります。 したがって、上記の例は、意味的に次と同等です。[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}終了サンプル
AttributeUsageには、 と呼ばれる名前付きパラメーター (AllowMultiple) があります。これは、特定のエンティティに対して属性を複数回指定できるかどうかを示します。 属性クラスの AllowMultiple が True の場合、その属性クラスは、multi-use 属性クラスであり、エンティティで複数回指定できます。 属性クラスの AllowMultiple が False または指定されていない場合、その属性クラスは single-use 属性クラスであり、エンティティに対して最大で 1 回指定できます。
例: 次の例では、
AuthorAttributeという名前の multi-use 属性クラスを定義し、Author属性の 2 つの使用方法を持つクラス宣言を示します。[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) => Name = name; } [Author("Brian Kernighan"), Author("Dennis Ritchie")] class Class1 { ... }終了サンプル
AttributeUsageには、 と呼ばれる別の名前付きパラメーター (Inherited) があります。これは、基底クラスで指定された属性がその基底クラスから派生したクラスによっても継承されるかどうかを示します。 属性クラスの Inherited が True の場合、その属性は継承されます。 属性クラスの Inherited が Frue の場合、その属性は継承されません。 指定されていない場合、既定値は True です。
次のように、属性クラス X に AttributeUsage 属性がアタッチされていない
class X : Attribute { ... }
これは、次と同じです。
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
23.2.3 位置指定パラメーターと名前付きパラメーター
属性クラスでは、位置パラメーターおよび名前付きパラメーターを使用できます。 属性クラスの各パブリック インスタンス コンストラクターは、その属性クラスの位置パラメーターの有効なシーケンスを定義します。 属性クラスの各非静的パブリック読み取り/書き込みフィールドと非静的パブリック読み取り/書き込みまたは読み取り init プロパティは、属性クラスの名前付きパラメーターを定義します。 プロパティで名前付きパラメーターを定義する場合、そのプロパティにはパブリック get アクセサーとパブリック セットまたは init アクセサーの両方が含まれます。
例: 次の例では、1 つの位置パラメーター、
HelpAttribute、および 1 つの名前付きパラメーター、urlを持つTopicという名前の属性クラスを定義します。 非静的でパブリックですが、Urlプロパティは、読み取り/書き込みまたは読み取り/init ではないため、名前付きパラメーターを定義しません。 この属性の 2 つの使用方法も示されています。[AttributeUsage(AttributeTargets.Class)] public class HelpAttribute : Attribute { public HelpAttribute(string url) // url is a positional parameter { ... } // Topic is a named parameter public string Topic { get; set; } public string Url { get; } } [Help("http://www.mycompany.com/xxx/Class1.htm")] class Class1 { } [Help("http://www.mycompany.com/xxx/Misc.htm", Topic ="Class2")] class Class2 { }終了サンプル
23.2.4 属性パラメーターの型
属性クラスの位置指定パラメーターと名前付きパラメーターの型は、 次の属性パラメーター型に制限されます。
- 次のいずれかの種類:
bool、byte、char、double、float、int、long、sbyte、short、string、uint、ulong、ushort。 - 型
object。 - 型
System.Type。 - 列挙型。
- 上記の型の 1 次元配列。
- これらの型のいずれかがないコンストラクター引数またはパブリック フィールドは、属性指定の位置パラメーターまたは名前付きパラメーターとして使用しません。
23.3 属性の指定
以前に定義した属性をプログラム エンティティに適用することは、 属性仕様と呼ばれます。 属性は、プログラム エンティティに対して指定される追加の宣言情報の一部です。 属性はグローバル スコープで指定できます (包含アセンブリまたはモジュールの属性を指定するため)、type_declarations (§14.8)、class_member_declarations (§15.3)、interface_member_declarations (§19. 4)、struct_member_declarations (§16.3)、enum_member_declarations (§20.2)、accessor_declarations (§15.7.3)、event_accessor_declarations (§15.8))、local_function_declarations (§13.6.4)、parameter_listの要素 (§15.6.2)、type_parameter_lists (§15.2.3)、lambda_expressions (§12.22.1)、および explicit_anonymous_function_parameters および implicit_anonymous_function_parameters の要素 (§12.22.1)。
属性は属性 セクションで指定されます。 属性セクションは、1 つ以上の属性のコンマ区切りのリストを囲む角かっこのペアで構成されます。 このようなリストで属性が指定される順序と、同じプログラム エンティティにアタッチされているセクションが配置される順序は重要ではありません。 たとえば、属性の仕様 [A][B]、[B][A]、[A, B]、および [B, A] は同等です。
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' global_attribute_target_specifier attribute_list ']'
;
global_attribute_target_specifier
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)* ','?
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
実稼働 global_attribute_target の場合、次のテキストでは、identifier に、assembly または module と等しいスペルが存在する必要があり、ここでの「等しい」もの、§6.4.3 で定義されています。 実稼働 attribute_targetの場合、次のテキストでは、上記と等値の定義を使用して、identifier に、assembly または module と等しくないスペルが存在する必要があります。
属性は attribute_name と、位置および名前付き引数の省略可能なリストで構成されます。 位置指定引数 (ある場合) は名前付き引数の前にあります。 位置引数は attribute_argument_expressionで構成されます。名前付き引数は、名前、等号、 attribute_argument_expressionで構成され、単純な代入と同じ規則によって制約されます。 名前付き引数の順序は重要ではありません。
注: 便宜上、array_initializer (§17.7) で使用できるのと同様に、global_attribute_section および attribute_section でもコンマを末尾で使用できます。 注釈
attribute_name は属性クラスを識別します。
属性をグローバル レベルで配置する場合は、global_attribute_target_specifier が必要です。 global_attribute_target が次と等しい場合:
-
assembly— ターゲットは包含アセンブリです -
module— ターゲットは包含モジュールです
global_attribute_target の他の値は使用できません。
標準化された attribute_target 名は、event、field、method、param、property、return、type、および typevar です。 これらのターゲット名は、次のコンテキストでのみ使用されます。
-
event— イベントです。 -
field— フィールドです。 フィールドに似たイベント (アクセサーのないイベント) (§15.8.2) と自動的に実装されるプロパティ (§15.7.4) も、このターゲットがある属性を持つことができます。 -
method— コンストラクター。ファイナライザー;メソッド;演算子;local 関数、プロパティ get、set、および init アクセサー。インデクサーの get、set、init アクセサー。アクセサーを追加および削除するイベント。およびラムダ式。 フィールドに似たイベント (アクセサーのないイベント) も、このターゲットがある属性を持つことができます。 -
param— プロパティ セットおよび init アクセサー、インデクサー セットおよび init アクセサー、イベントの追加および削除アクセサー、およびコンストラクター、メソッド、ローカル 関数、および演算子のパラメーター。 -
property— プロパティとインデクサー。 -
return— デリゲート、メソッド、ローカル関数、演算子、プロパティ取得アクセサー、インデクサー取得アクセサー、ラムダ式。 -
type— デリゲート、クラス、構造体、列挙型、およびインターフェイス。 -
typevar— type パラメーター。
特定のコンテキストでは、複数のターゲットに対する属性の指定が許可されます。 プログラムは、attribute_target_specifier を含めることで、ターゲットを明示的に指定できます。 attribute_target_specifier がない場合は、既定値が適用されますが、attribute_target_specifier を使用すると既定値を確認またはオーバーライドできます。 コンテキストは次のように解決されます。
- デリゲート宣言の属性の場合、既定のターゲットはデリゲートです。 それ以外の場合で attribute_target が次と等しい場合:
-
type— ターゲットはデリゲートです -
return— ターゲットは戻り値です
-
- メソッド宣言の属性の場合、既定のターゲットはメソッドです。 それ以外の場合で attribute_target が次と等しい場合:
-
method— ターゲットはメソッドです -
return— ターゲットは戻り値です
-
- ローカル関数宣言の属性の場合、既定のターゲットはローカル関数です。 それ以外の場合で attribute_target が次と等しい場合:
-
method— ターゲットはローカル関数です -
return— ターゲットは戻り値です
-
- 演算子宣言の属性の場合、既定のターゲットは演算子です。 それ以外の場合で attribute_target が次と等しい場合:
-
method— ターゲットは演算子です。 -
return— ターゲットは戻り値です
-
- プロパティ宣言またはインデクサー宣言の get アクセサー宣言の属性の場合、既定のターゲットは関連付けられたメソッドです。 それ以外の場合で attribute_target が次と等しい場合:
-
method— ターゲットは関連付けられたメソッドです。 -
return— ターゲットは戻り値です
-
- プロパティまたはインデクサー宣言の set アクセサーまたは init アクセサーで指定された属性の場合、既定のターゲットは関連付けられたメソッドです。 それ以外の場合で attribute_target が次と等しい場合:
-
method— ターゲットは関連付けられたメソッドです。 -
param— ターゲットは、孤立した暗黙的パラメーターです。
-
- 自動的に実装されるプロパティ宣言の属性の場合、既定のターゲットはプロパティです。 それ以外の場合で attribute_target が次と等しい場合:
-
field— ターゲットは、プロパティのコンパイラーによって生成されたバッキング フィールドです
-
-
event_accessor_declarations を省略したイベント宣言で指定された属性の場合、既定のターゲットは、イベント宣言です。 それ以外の場合で attribute_target が次と等しい場合:
-
event— ターゲットはイベント宣言です -
field— ターゲットはフィールドです。 -
method— ターゲットはメソッドです。
-
-
event_accessor_declarations を省略していないイベント宣言の場合、既定のターゲットは、メソッドです。
-
method— ターゲットは関連付けられたメソッドです。 -
param— ターゲットは、孤立したパラメーターです。
-
-
lambda_expressionの属性の場合、既定のターゲットはメソッドです。 それ以外の場合で attribute_target が次と等しい場合:
-
method— ターゲットはメソッドです -
return— ターゲットは戻り値です
-
他のすべてのコンテキストでは、attribute_target_specifier を含めることはできますが、含める必要はありません。
例: クラス宣言には、指定子
typeを含めるめることができますが、省略できます。[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}サンプルの終了。
実装は他の attribute_targetを受け入れることができ、その目的は実装が定義されています。 このような attribute_target を認識しない実装では、警告が表示され、含まれている attribute_section を無視します。
慣例により、属性クラスには Attribute のサフィックスを付けた名前が付けられます。
attribute_name には、このサフィックスを含めるめることができますが、省略できます。 具体的には、attribute_name は次のように解決されます。
-
attribute_name の右端の識別子が逐語的識別子 (§6.4.3) の場合、attribute_name は、type_name (§7.8) として解決されます。 結果が
System.Attributeから派生した型でない場合は、コンパイル時エラーが発生します。 - それ以外の場合:
上記の 2 つの手順のうちの 1 つで System.Attributeから派生した型が得られる場合、その型は attribute_name の結果になります。 それ以外の場合は、コンパイル時エラーが発生します。
例: このサフィックスの有無にかかわらず属性クラスが見つかった場合、あいまいさが存在し、コンパイル時エラーが発生します。 attribute_name の右端の識別子が逐語的識別子 (§6.4.3) である場合、サフィックスのない属性のみが一致するため、このようなあいまいさが解決されます。 例
[AttributeUsage(AttributeTargets.All)] public class Example : Attribute {} [AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Error: ambiguity class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Refers to Example class Class3 {} [@ExampleAttribute] // Refers to ExampleAttribute class Class4 {}には、
ExampleとExampleAttributeという名前の 2 つの属性クラスが表示されます。 属性[Example]は、ExampleまたはExampleAttributeを参照する可能性があるため、あいまいです。 逐語的識別子を使用すると、このようなまれなケースで正確なインテントを指定できます。 属性[ExampleAttribute]はあいまいではありません (ただし、ExampleAttributeAttribute!という名前の属性クラスがあった場合です)。 クラスExampleの宣言が削除された場合、次のように、両方の属性がExampleAttributeという名前の属性クラスを参照します。[AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Refers to ExampleAttribute class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Error: no attribute named “Example” class Class3 {}終了サンプル
同じエンティティで single-use 属性クラスを複数回使用すると、コンパイル時エラーになります。
例: 例
[AttributeUsage(AttributeTargets.Class)] public class HelpStringAttribute : Attribute { public HelpStringAttribute(string value) { Value = value; } public string Value { get; } } [HelpString("Description of Class1")] [HelpString("Another description of Class1")] // multiple uses not allowed public class Class1 {}は、single-use 属性クラスである
HelpStringをClass1の宣言で複数回使用しようとするため、コンパイル時エラーが発生します。終了サンプル
式 E は、次のすべてのステートメントが true の場合、attribute_argument_expression です。
-
Eの型は属性パラメーター型 (§23.2.4) です。 - コンパイル時に、
Eの値を次のいずれかに解決できます。
例:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)] public class TestAttribute : Attribute { public int P1 { get; set; } public Type P2 { get; set; } public object P3 { get; set; } } [Test(P1 = 1234, P3 = new int[]{1, 3, 5}, P2 = typeof(float))] class MyClass {} class C<T> { [Test(P2 = typeof(T))] // Error – T not a closed type. int x1; [Test(P2 = typeof(C<T>))] // Error – C<;T>; not a closed type. int x2; [Test(P2 = typeof(C<int>))] // Ok int x3; [Test(P2 = typeof(C<>))] // Ok int x4; }終了サンプル
複数の部分で宣言された型の属性は、各部分の属性を不特定の順序で組み合わせることによって決定されます。 同じ属性が複数の部分に配置されている場合、その属性を型に複数回指定することと同じです。
例: 2 つの部分:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}は、次の単一宣言と同等です。
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}終了サンプル
型パラメーターの属性は、同じ方法で結合されます。
23.4 属性インスタンス
23.4.1 全般
属性インスタンスは、実行時に属性を表すインスタンスです。 ぞページ区政は、属性クラス、位置引数、名前付き引数で定義されます。 属性インスタンスは、位置引数と名前付き引数で初期化される属性クラスのインスタンスです。
属性インスタンスの取得には、次のサブクラスで説明するように、コンパイル時と実行時の両方の処理が含まれます。
23.4.2 属性のコンパイル
属性クラス のT、positional_argument_listP、named_argument_listN、およびプログラム エンティティで指定されたエンティティ E のコンパイルは、次の手順を実行してアセンブリ A にコンパイルされます。
- 新しいフォーム の
T(P)をコンパイルするには、コンパイル時の処理手順を実行します。 これらの手順では、コンパイル時エラーが発生するか、実行時に呼び出すことができるCの インスタンス コンストラクターTを特定します。 -
Cにパブリック アクセシビリティがない場合は、コンパイル時エラーが発生します。 -
の各
ArgNの場合:-
Nameを named_argument のArgにします。 -
Nameは、Tの非静的読み取り/書き込みパブリック フィールドまたは静的でない静的読み取り/書き込みまたは読み取り init プロパティを識別する必要があります。Tにそのようなフィールドまたはプロパティがない場合は、コンパイル時エラーが発生します。
-
-
positional_argument_list内の値
Pまたはnamed_argument_list内のいずれかの値NがSystem.String型で、Unicode 標準で定義されているとおりに適切な形式でない場合、コンパイルされた値が取得された実行時の値 (§23.4.3) と等しいかどうかが実装で定義されます。注: 例として、上位サロゲート UTF-16 コード単位を含む文字列の直後に低サロゲート コードユニットが続かない文字列は整形式ではありません。 注釈
- 属性を含むプログラムをコンパイルした結果として、コンパイラが出力するアセンブリに (属性の実行時インスタンス化のために) 属性クラス
T、Cのインスタンス コンストラクターT、positional_argument_listP、named_argument_listN、コンパイル時に完全に解決された値を含む関連するプログラム エンティティEの情報を格納します。
23.4.3 属性インスタンスの実行時の取得
§23.4.2 で定義されている用語を使用すると、T、C、P、およびNで表され、Eに関連付けられた属性インスタンスは、次の手順を使用して、アセンブリ Aから実行時に取得できます。
- コンパイル時に決定されたインスタンス コンストラクター を使用して、形式
new T(P)のCを実行するための実行時処理手順を実行します。 これらの手順では、例外が発生するか、OのインスタンスTが生成されます。 -
の各
ArgNの場合、次の順序に従います。-
Nameを named_argument のArgにします。Nameが、Oで非静的パブリック読み取り/書き込みフィールドまたは非静的パブリック読み取り/書き込みまたは読み取り/init プロパティを識別しない場合は、例外がスローされます。 -
Valueを のArgの評価した結果にします。 -
NameがOでフィールドを識別しない場合は、このフィールドをValueに設定します。 - それ以外の場合、Name は
Oのプロパティを識別します。 このプロパティを Value に設定します。 - 結果は、
O、TおよびPで初期化された属性クラスNのインスタンスです。
-
注:
TにC、P、N、E(およびAに関連付ける) を格納する形式およびEを指定するメカニズムおよび、Tから、C、P、N、Aを取得するメカニズム (したがって、属性インスタンスが実行時にどのように取得されるか) は、この仕様の範囲外です。 注釈
23.5 予約済み属性
23.5.1 全般
一部の属性が何らかの方法で言語に影響します。 以下のような属性があります。
-
System.AttributeUsageAttribute(§23.5.2)。 属性クラスを使用する方法を記述するために使用されます。 -
System.Diagnostics.ConditionalAttribute(§23.5.3) は、条件付きメソッドと条件属性クラスを定義するために使用されるマルチユース属性クラスです。 この属性は、条件付きコンパイル シンボルをテストすることによって条件を示します。 -
System.ObsoleteAttribute(§23.5.4) は、メンバーを古いものとしてマークするために使用されます。 -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5)。 非同期メソッドのタスク ビルダーを確立するために使用されます。 -
System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2)、System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3)、System.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4)、およびSystem.Runtime.CompilerServices.CallerArgumentExpressionAttribute(§23.5.6.5) は、呼び出し元コンテキストに関する情報を省略可能なパラメーターに提供するために使用されます。 -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8)。 非同期反復子でキャンセル トークンのパラメーターを指定するために使用されます。 -
System.Runtime.CompilerServices.ModuleInitializer(§23.5.9)。 メソッドをモジュール初期化子としてマークするために使用されます。 -
System.Runtime.CompilerServices.InterpolatedStringHandlerAttributeおよびSystem.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute。これは、カスタム補間文字列式ハンドラー (§23.5.9.1) を宣言し、そのコンストラクターの 1 つをそれぞれ呼び出すために使用されます。
Null 許容静的分析属性 (§23.5.7) を使用すると、nullabilities および null 状態に対して生成される警告の正確性を向上させることができます (§8.9.5)。
実行環境では、C# プログラムの実行に影響を与える追加の実装定義属性が指定される場合があります。
23.5.2 AttributeUsage 属性
属性 AttributeUsage は、属性クラスを使用できる方法を記述するために使用されます。
AttributeUsage 属性で修飾されたクラスは、直接または間接的に System.Attribute から派生する必要があります。 それ以外の場合は、コンパイル時エラーが発生します。
注: この属性の使用例については、 §23.2.2 を参照してください。 注釈
23.5.3 条件属性
23.5.3.1 全般
属性 Conditional では、 条件付きメソッドs、 条件付きローカル関数、および 条件付き属性クラスes の定義が可能になります。
23.5.3.2 条件付きメソッド
Conditional 属性で修飾されたメソッドは、条件付きメソッドです。 したがって、各条件付きメソッドは、Conditional 属性で宣言された条件付きコンパイル シンボルに関連付けられます。
例:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }は、
Eg.MとALPHAの 2 つの条件付きコンパイル シンボルに関連付けられた条件付きメソッドとしてBETAを宣言します。終了サンプル
呼び出し時に関連付けられている条件付きコンパイル シンボルの 1 つ以上が定義されている場合は、条件付きメソッドの呼び出しが含まれます。それ以外の場合、呼び出しは省略されます。
条件付きメソッドには、次の制限が適用されます。
- 条件付きメソッドは、class_declaration または struct_declaration 内のメソッドである必要があります。
Conditional属性がインターフェイス宣言のメソッドで指定されている場合、コンパイル時エラーが発生します。 - 条件付きメソッドは、プロパティ、インデクサー、またはイベントのアクセサーではありません。
- 条件付きメソッドには
voidの戻り値の型があります。 - 条件付きメソッドは、
override修飾子でマークされません。 ただし、条件付きメソッドは、virtual修飾子でマークできます。 このようなメソッドのオーバーライドは暗黙的に条件付きで、Conditional属性で明示的にマークすることはできません。 - 条件付きメソッドは、インターフェイス メソッドの実装ではありません。 それ以外の場合は、コンパイル時エラーが発生します。
- 条件付きメソッドのパラメーターは、出力パラメーターにすることはできません。
注:
AttributeUsageを含む (AttributeTargets.Method) を持つ属性は、通常、プロパティ、インデクサー、およびイベントのアクセサーに適用できます。 上記の制限により、このConditional属性の使用は禁止されています。 注釈
さらに、条件付きメソッドからデリゲートが作成されると、コンパイル時エラーが発生します。
例: 例
#define DEBUG using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void M() { Console.WriteLine("Executed Class1.M"); } } class Class2 { public static void Test() { Class1.M(); } }は、
Class1.Mを条件付きメソッドとして宣言します。Class2のTestメソッドは、このメソッドを呼び出します。 条件付きコンパイル シンボルDEBUGが定義されているため、Class2.Testが呼び出されると、Mが呼び出されます。 シンボルDEBUGが定義されていない場合、Class2.TestはClass1.Mを呼び出しません。終了サンプル
条件付きメソッドの呼び出しの包含または除外は、呼び出しの時点で条件付きコンパイル シンボルによって制御されることを理解しておくことが重要です。
例: 次のコード例の内容:
// File Class1.cs: using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void F() { Console.WriteLine("Executed Class1.F"); } } // File Class2.cs: #define DEBUG class Class2 { public static void G() { Class1.F(); // F is called } } // File Class3.cs: #undef DEBUG class Class3 { public static void H() { Class1.F(); // F is not called } }各クラス
Class2およびClass3には、Class1.Fが定義されているかどうかに基づく条件付きメソッドDEBUGの呼び出しが含まれます。 このシンボルはClass2のコンテキストで定義されますが、Class3では定義されていないため、FのClass2の呼び出しは含まれますが、FのClass3の呼び出しは省略されます。終了サンプル
継承チェーンで条件付きメソッドを使用すると、混乱を招く可能性があります。 形式 base の base.M 経由で呼び出された条件付きメソッドは、通常の条件付きメソッド呼び出しルールが適用されます。
例: 次のコード例の内容:
// File Class1.cs using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public virtual void M() => Console.WriteLine("Class1.M executed"); } // File Class2.cs class Class2 : Class1 { public override void M() { Console.WriteLine("Class2.M executed"); base.M(); // base.M is not called! } } // File Class3.cs #define DEBUG class Class3 { public static void Main() { Class2 c = new Class2(); c.M(); // M is called } }
Class2には、基底クラスで定義されているMの呼び出しが含まれています。 基本メソッドは、未定義のシンボルDEBUGの存在に基づいて条件付きであるため、この呼び出しは省略されます。 したがって、メソッドはコンソール "Class2.M executed" にのみ書き込みます。 pp_declaration を慎重に使用することで、このような問題を排除できます。終了サンプル
23.5.3.3 条件付きローカル関数
静的ローカル関数は、条件付きメソッド (§23.5.3.2) と同じ意味で条件付きにすることができます。
非静的ローカル関数が条件付きになった場合、コンパイル時エラーが発生します。
23.5.3.4 条件付き属性クラス
1 つ以上の属性で修飾された属性クラス (Conditional) は、条件付き属性クラスです。 したがって、各条件付きクラスは、Conditional 属性で宣言された条件付きコンパイル シンボルに関連付けられます。
例:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttributeは、条件付きコンパイルシンボルALPHAおよびBETAに関連付けられた条件付き属性クラスとして宣言します。終了サンプル
条件属性の属性仕様 (§23.3) は、関連付けられている条件付きコンパイル シンボルの 1 つ以上が仕様の時点で定義されている場合に含まれます。それ以外の場合、属性の指定は省略されます。
条件付き属性クラスの属性仕様の包含または除外は、仕様の時点で条件付きコンパイル シンボルによって制御されることに注意してください。
例: この例では
// File Test.cs: using System; using System.Diagnostics; [Conditional("DEBUG")] public class TestAttribute : Attribute {} // File Class1.cs: #define DEBUG [Test] // TestAttribute is specified class Class1 {} // File Class2.cs: #undef DEBUG [Test] // TestAttribute is not specified class Class2 {}クラス
Class1とClass2はそれぞれ、属性Testで修飾されます。これは、DEBUGが定義されているかどうかに基づいて条件付きになります。 このシンボルは、Class1のコンテキストで定義されており、Class2ではないため、Class1のテスト属性の仕様が含まれる一方で、TestのClass2属性の仕様は省略されます。終了サンプル
23.5.4 廃止された属性
Obsolete 属性は、使用されなくなった型の型とメンバーをマークするために使用されます。
プログラムが Obsolete 属性で修飾された型またはメンバーを使用していると、コンパイラは警告またはエラーを発行します。 具体的には、エラー パラメーターが指定されていない場合、またはエラー パラメーターが指定されていて値が false の場合、コンパイラは警告を発行します。 エラー パラメーターが指定されていて値が true の場合、コンパイラはエラーを発行します。
例: 次のコード例の内容:
[Obsolete("This class is obsolete; use class B instead")] class A { public void F() {} } class B { public void F() {} } class Test { static void Main() { A a = new A(); // Warning a.F(); } }クラス
Aは、Obsolete属性で修飾されます。AのMainを使用すると、"このクラスは廃止されました。代わりにクラスBを使用してください。" という指定されたメッセージが含まれる警告が表示されます。終了サンプル
23.5.5 AsyncMethodBuilder 属性
この属性については、 §15.14.1 を参照してください。
23.5.6 呼び出し元情報属性
23.5.6.1 全般
ログ記録やレポートなどの目的で、関数メンバーが呼び出し元のコードに関する特定のコンパイル時情報を取得すると便利な場合があります。 caller-info 属性は、このような情報を透過的に渡す方法を提供します。
省略可能なパラメーターにいずれかの caller-info 属性が注釈付けされている場合、呼び出しで対応する引数を省略しても、既定のパラメーター値が置き換えられるとは限りません。 代わりに、呼び出し元のコンテキストに関する指定された情報が使用可能な場合、その情報は引数値として渡されます。
例:
public void Log( [CallerLineNumber] int line = -1, [CallerFilePath] string path = null, [CallerMemberName] string name = null ) { Console.WriteLine((line < 0) ? "No line" : "Line "+ line); Console.WriteLine((path == null) ? "No file path" : path); Console.WriteLine((name == null) ? "No member name" : name); }引数を指定しない
Log()を呼び出すと、呼び出しの行番号とファイル パス、および呼び出しが発生したメンバーの名前が出力されます。終了サンプル
caller-info 報属性は、デリゲート宣言を含め、任意の場所で省略可能なパラメーターで発生する可能性があります。 ただし、特定の caller-info 属性には、属性として使用できるパラメーターの型に制限があるため、置換された値からパラメーター型への暗黙的な変換が常に存在します。
部分メソッド宣言の定義部分と実装部分の両方のパラメーターに対して同じ caller-info 属性を持つとエラーになります。 定義パーツ内の caller-info 属性のみが適用されますが、実装パーツでのみ発生する caller-info 属性は無視されます。
呼び出し元の情報は、オーバーロードの解決には影響しません。 属性付き省略可能パラメーターは呼び出し元のソース コードから引き続き省略されるため、オーバーロードの解決では、省略された他の省略可能なパラメーター (§12.6.4) を無視するのと同じ方法でこれらのパラメーターが無視されます。
呼び出し元情報は、ソース コードで関数が明示的に呼び出された場合にのみ置き換わります。 暗黙的な親コンストラクター呼び出しなどの暗黙的な呼び出しにはソースの場所がないため、呼び出し元の情報は置き換られません。 また、動的にバインドされた呼び出しは、呼び出し元情報に代わるものではありません。 このような場合に caller-info 属性パラメーターを省略すると、パラメーターの指定された既定値が代替使用されます。
例外の 1 つとして、クエリ式が挙げられます。 これらは構文拡張とみなされ、呼び出し元が展開して、caller-info 属性を持つ省略可能なパラメーターを省略すると、呼び出し元情報が置き換えられます。 使用される場所は、呼び出しが生成されたクエリ句の場所です。
指定されたパラメーターに複数の呼び出し元情報属性が指定されている場合は、 CallerLineNumber、 CallerFilePath、 CallerMemberName、 CallerArgumentExpressionの順序で認識されます。 次のパラメーター宣言について考えてみましょう。
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber が優先され、他の 3 つの属性は無視されます。
CallerLineNumberを省略すると、CallerFilePathが優先され、CallerMemberNameとCallerArgumentExpressionは無視されます。 これらの属性の字句の順序は関係ありません。
23.5.6.2 CallerLineNumber 属性
定数値 System.Runtime.CompilerServices.CallerLineNumberAttribute からパラメーターの型への標準の暗黙的な変換 (§10.4.2) がある場合、省略可能なパラメーターで属性 int.MaxValue が許可されます。 これにより、その値までの負以外の行番号をエラーなしで渡すことができます。
ソース コード内の場所からの関数呼び出しで、CallerLineNumberAttribute を使用した省略可能なパラメーターが省略されている場合、その場所の行番号を表す数値リテラルが、既定のパラメーター値ではなく、呼び出しの引数として使用されます。
呼び出しが複数行にまたがる場合、選択された行は実装に依存します。
行番号は、#line ディレクティブ (§6.5.8) の影響を受ける場合があります。
23.5.6.3 CallerFilePath 属性
System.Runtime.CompilerServices.CallerFilePathAttribute からパラメーターの型への標準の暗黙的な変換 (§10.4.2) がある場合、省略可能なパラメーターで属性 string が許可されます。
ソース コード内の場所からの関数呼び出しで、CallerFilePathAttribute を使用した省略可能なパラメーターが省略されている場合、その場所のファイル パスを表す文字列リテラルが、既定のパラメーター値ではなく、呼び出しの引数として使用されます。
ファイル パスの形式は実装に依存します。
ファイル パスは、#line ディレクティブ (§6.5.8) の影響を受ける場合があります。
23.5.6.4 CallerMemberName 属性
System.Runtime.CompilerServices.CallerMemberNameAttribute からパラメーターの型への標準の暗黙的な変換 (§10.4.2) がある場合、省略可能なパラメーターで属性 string が許可されます。
関数メンバーの本文内の場所または、関数メンバー自体に適用された属性内またはその戻り値の型、ソース コード内のパラメーターまたは型パラメーターからの関数呼び出しが CallerMemberNameAttribute を使用して省略された場合、そのメンバーを表す文字列リテラルは、既定のパラメーター値ではなく、呼び出しへの引数として使用されます。 (最上位レベルのステートメント (§7.1.3) からの関数呼び出しの場合、メンバー名は実装によって生成されます)。
ジェネリック メソッド内で発生する呼び出しの場合、型パラメーター リストを使用せずに、メソッド名自体のみが使用されます。
明示的なインターフェイス メンバーの実装内で発生する呼び出しの場合は、前のインターフェイス修飾なしで、メソッド名自体のみが使用されます。
プロパティまたはイベント アクセサー内で発生する呼び出しの場合、使用されるメンバー名はプロパティまたはイベント自体のメンバー名です。
インデクサー アクセサー内で発生する呼び出しの場合、使用されるメンバー名は、インデクサー メンバーの IndexerNameAttribute (§23.6) によって提供されます (存在する場合)。それ以外の場合は既定の名前 Item 。
フィールド初期化子またはイベント初期化子内で発生する呼び出しの場合、使用されるメンバー名は初期化されるフィールドまたはイベントの名前です。
インスタンス コンストラクター、静的コンストラクター、ファイナライザー、および演算子の宣言内で発生する呼び出しの場合、使用されるメンバー名は実装に依存します。
ローカル関数または匿名関数内で発生する呼び出しの場合、その関数を呼び出すメンバー メソッドの名前が使用されます。
例: 以下を確認してください。
class Program { static void Main() { F1(); void F1([CallerMemberName] string? name = null) { Console.WriteLine($"F1 MemberName: |{name}|"); F2(); } static void F2([CallerMemberName] string? name = null) { Console.WriteLine($"F2 MemberName: |{name}|"); } } }出力を生成する
F1 MemberName: |Main| F2 MemberName: |Main|この属性は呼び出し元の関数メンバーの名前を提供します。ローカル関数
F1はメソッドMainです。 また、F2がF1によって呼び出されても、ローカル関数は関数メンバー ではないため 、F2の報告された呼び出し元もMain。 終了サンプル
23.5.6.5 CallerArgumentExpression 属性
属性 System.Runtime.CompilerServices.CallerArgumentExpressionAttribute は ターゲット パラメーターに適用され、兄弟パラメーターの引数のソース コード テキストが文字列としてキャプチャされ、ここで キャプチャされた文字列と呼ばれる場合があります。
拡張メソッドの最初のパラメーターである場合を除き、ターゲット パラメーターは default_argumentを持つ必要があります。
次のメソッド宣言について考えてみましょう。
using System;
using System.Runtime.CompilerServices;
#nullable enable
class Test
{
public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null)
{
Console.WriteLine($"val = {val}, text = <{text}>");
}
}
ターゲット パラメーターがtextされ、兄弟パラメーターがvalされ、M呼び出されたときに対応する引数のソース コード テキストをtextにキャプチャできます。
属性コンストラクターは、 string型の引数を受け取ります。 その文字列
- 兄弟パラメーターの名前を含む必要があります。それ以外の場合、属性は無視されます。
- そのプレフィックスを持つパラメーター名から先頭の
@を省略します。
parameter_listには、複数のターゲット パラメーターが含まれている場合があります。
ターゲット パラメーターの型は、 stringからの標準変換を持つ必要があります。
メモ: つまり、
stringからのユーザー定義の変換は許可されません。実際には、このようなパラメーターの型は、string、object、またはstringによって実装されるインターフェイスである必要があります。 注釈
ターゲット パラメーターに明示的な引数が渡された場合、文字列はキャプチャされません。そのパラメーターはその引数の値を受け取ります。 それ以外の場合、兄弟パラメーターに対応する引数のテキストは、次の規則に従ってキャプチャされた文字列に変換されます。
- 先頭と末尾の空白は、最も外側のグループ化かっこが削除される前と後の両方で削除されます。
- 最も外側のグループ化かっこはすべて、先頭と末尾の空白が削除される前と後の両方で削除されます。
- その他のすべての input_elementは、逐語的に保持されます (空白、コメント、 Unicode_Escape_Sequence、識別子の
@プレフィックスを含む)。
キャプチャされた文字列は、ターゲット パラメーターに対応する引数として渡されます。 ただし、兄弟パラメーターの引数を省略すると、ターゲット パラメーターはその default_argument 値を受け取ります。
例: 上記の
M宣言を考えて、Mに対する次の呼び出しを検討してください。Test.M(); Test.M(123); Test.M(123, null); Test.M(123, "xyz"); Test.M( 1 + 2 ); Test.M(( ( (123) + 0) ) ); int local = 10; Test.M(l\u006fcal /*...*/ + // xxx 5);生成される出力は次のとおりです。
val = 0, text = <> val = 123, text = <123> val = 123, text = <> val = 123, text = <xyz> val = 3, text = <1 + 2> val = 123, text = <(123) + 0> val = 15, text = <l\u006fcal /*...*/ + // xxx 5>終了サンプル
23.5.7 コード分析属性
23.5.7.1 全般
このサブクラスの属性は、null 値の許容と null 状態の診断 (§8.9.5) を提供するコンパイラをサポートするための追加情報を提供するために使用されます。 コンパイラは、null 状態の診断を実行する必要はありません。 これらの属性の有無は、言語やプログラムの動作には影響しません。 null 状態の診断を提供しないコンパイラは、これらの属性の存在を読み取り、無視する必要があります。 null 状態の診断を提供するコンパイラは、診断を通知するために使用するこれらの属性に対して、このサブクラスで定義されている意味を使用する必要があります。
code-analysis 属性は、名前空間 System.Diagnostics.CodeAnalysis で宣言されます。
| 属性 | 意味 |
|---|---|
AllowNull (§23.5.7.2) |
null 非許容の引数を null にすることができます。 |
DisallowNull (§23.5.7.3) |
null 許容の引数を null にすることはできません。 |
MaybeNull (§23.5.7.6) |
null 非許容の戻り値は null である可能性があります。 |
NotNull (§23.5.7.10) |
null 許容の戻り値が null になることはありません。 |
MaybeNullWhen (§23.5.7.7) |
メソッドから指定された bool 値が返されるとき、null 非許容の引数が null である可能性があります。 |
NotNullWhen (§23.5.7.12) |
メソッドが指定した bool 値を返す場合、null 許容引数は null になりません。 |
NotNullIfNotNull (§23.5.7.11) |
指定されたパラメーターの引数が null でない場合、戻り値は null ではありません。 |
MemberNotNull (§23.5.7.8) |
リストされているメンバーは、メソッドが返されるときに null になりません。 |
MemberNotNullWhen (§23.5.7.9) |
メソッドが指定した bool 値を返す場合、リストされているメンバーは null になりません。 |
DoesNotReturn (§23.5.7.4) |
このメソッドは返されません。 |
DoesNotReturnIf (§23.5.7.5) |
関連付けられた bool パラメーターに指定された値がある場合、このメソッドから制御が返されることはありません。 |
§23.5.7 の次のサブクラウスは条件付きで規範的です。
23.5.7.2 AllowNull 属性
対応する型で禁止されていても、null 値の入力を許可することを指定します。
例: 切な既定値が設定されているため、
nullを返さない、次の読み取りおよび書き込みプロパティを考慮します。 ただし、ユーザーは set アクセサーに null を指定して、プロパティをその既定値に設定できます。#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }そのプロパティの set アクセサーの次の使用を考えると
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNullこの属性が指定されていないと、null 非許容型のプロパティが null 値に設定されているように解釈され、コンパイラが警告を生成する可能性があります。 属性が存在すると、その警告は抑制されます。 終了サンプル
23.5.7.3 DisallowNull 属性
対応する型で許可されいても、null 値の入力を禁止することを指定します。
例: null が既定値ですが、クライアントは null 以外の値にのみ設定できる次のプロパティを考慮します。
#nullable enable public class X { [DisallowNull] public string? ReviewComment { get => _comment; set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null"); } private string? _comment = default; }get アクセサは既定値の
nullを返す可能性があるため、アクセス前にチェックが必要であるとコンパイラから警告される場合があります。 さらに、呼び出し元は、null であっても、呼び出し元が明示的に null に設定しないように警告します。 終了サンプル
23.5.7.4 DoesNotReturn 属性
特定のメソッドが返されないように指定します。
例: 以下を確認してください。
public class X { [DoesNotReturn] private void FailFast() => throw new InvalidOperationException(); public void SetState(object? containedField) { if ((!isInitialized) || (containedField == null)) { FailFast(); } // null check not needed. _field = containedField; } private bool isInitialized = false; private object _field; }この属性の存在は、コンパイラにとってさまざまな点で役立ちます。 まず、例外をスローせずにメソッドを終了できるパスがある場合、コンパイラは警告を発行できます。 2 番目に、コンパイラは、適切な catch 句が見つかるまで、そのメソッドの呼び出し後のすべてのコードで null 許容警告を抑制できます。 3 つ目は、到達できないコードは null 状態には影響しません。
属性は、この属性の存在に基づいて到達可能性 (§13.2) または確定代入 (§9.4) 分析を変更しません。 これは、null 許容の警告に影響を与えるためだけに使用されます。 終了サンプル
23.5.7.5 DoesNotReturnIf 属性
関連付けられている bool パラメーターに指定した値がある場合、特定のメソッドが返されないように指定します。
例: 以下を確認してください。
#nullable enable public class X { private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName) { if (isNull) { throw new ArgumentException(argumentName, $"argument {argumentName} cannot be null"); } } public void SetFieldState(object containedField) { ThrowIfNull(containedField == null, nameof(containedField)); // unreachable code when "isInitialized" is false: _field = containedField; } private bool isInitialized = false; private object _field = default!; }終了サンプル
23.5.7.6 MaybeNull 属性
null 許容以外の戻り値を null にするように指定します。
例: 次の一般的なメソッドを考慮します。
#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }属性がない場合、メソッドが
nullを返す可能性がある場合、コンパイラは警告を生成する可能性があります。 属性が存在すると、その警告は抑制されます。 終了サンプル
23.5.7.7 MaybeNullWhen 属性
メソッドが、指定された null 値を返す場合、null 許容以外の引数を bool にすることを指定します。 これは MaybeNull 属性 (§23.5.7.6) に似ていますが、指定された戻り値のパラメーターが含まれています。
23.5.7.8 MemberNotNull 属性
メソッドが戻るときに、指定されたメンバーが null されないように指定します。
例: ヘルパー メソッドには、
MemberNotNull属性を含め、そのメソッド内の null 以外の値に割り当てられているフィールドを一覧表示できます。 コンストラクターを分析して、null 非許容参照フィールドがすべて初期化されているかどうかを判断するコンパイラでは、この属性を使用して、これらのヘルパー メソッドによって設定されているフィールドを検出できます。 次の例を確認してください。#nullable enable public class Container { private string _uniqueIdentifier; // must be initialized. private string? _optionalMessage; public Container() { Helper(); } public Container(string message) { Helper(); _optionalMessage = message; } [MemberNotNull(nameof(_uniqueIdentifier))] private void Helper() { _uniqueIdentifier = DateTime.Now.Ticks.ToString(); } }複数のフィールド名を属性のコンストラクターの引数として指定できます。 終了サンプル
23.5.7.9 MemberNotNullWhen 属性
メソッドが指定したnull値を返すときに、リストされているメンバーがboolされないように指定します。
例: この属性は
MemberNotNull(§23.5.7.8) のようになりますが、MemberNotNullWhenはbool引数を受け取ります。MemberNotNullWhenは、ヘルパー メソッドがフィールドを初期化したかどうかを示すboolを返す状況で使用するためのものです。 終了サンプル
23.5.7.10 NotNull 属性
メソッドが (スローではなく) 返された場合に null 許容値が null されないように指定します。
例: 以下を確認してください。
#nullable enable public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") => _ = value ?? throw new ArgumentNullException(valueExpression); public static void LogMessage(string? message) { ThrowWhenNull(message, nameof(message)); Console.WriteLine(message.Length); }null 許容参照型が有効な場合、メソッド
ThrowWhenNullは警告なしでコンパイルされます。 そのメソッドが戻ったときに、value引数がnullでないことが保証されます。 ただし、null 参照を使用してThrowWhenNullを呼び出してもかまいません。 終了サンプル
23.5.7.11 NotNullIfNotNull 属性
指定したパラメーターの引数がnullされていない場合、戻り値がnullされないことを指定します。
例: 戻り値の null 状態は、1 つまたは複数の引数の null 状態に依存するできます。 特定の引数が
nullでないときにメソッドが常に null 以外の値を返す場合のコンパイラの分析を支援するために、NotNullIfNotNull属性を使用できます。 次のメソッドがあるとします。#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
url引数がnullされていない場合、nullは返されません。 null 許容参照を有効にすると、API で null 引数が受け入れられることがない場合、そのシグネチャは正常に機能します。 しかし、引数が null である可能性がある場合は、戻り値も null である可能性があります。 そのコントラクトを正しく表現するには、次のようにこのメソッドに注釈を付けます。#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }終了サンプル
23.5.7.12 NotNullWhen 属性
メソッドが指定したnull値を返すときに、null 許容引数がboolされないことを指定します。
例: ライブラリ メソッド
String.IsNullOrEmpty(String)は、引数がtrueまたは空の文字列である場合にnullを返します。 これは null チェックの形式です。メソッドがfalseを返す場合、呼び出し元は引数を null チェックする必要はありません。 このようなメソッドを null 許容にするには、パラメーターの型を null 許容参照型にし、NotNullWhen 属性を追加します。#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }終了サンプル
23.5.8 EnumeratorCancellation 属性
非同期反復子 (CancellationToken) のを表すパラメーターを指定します。 このパラメーターの引数は、 IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken)に渡される引数と組み合わせる必要があります。 この結合トークンは、 IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2) によってポーリングされます。 トークンは、 CancellationToken.CreateLinkedTokenSource とその Token プロパティのように 1 つのトークンに結合されます。 2 つのソース トークンのいずれかが取り消されると、結合されたトークンが取り消されます。 結合されたトークンは、そのメソッドの本体の非同期反復子メソッド (§15.15) の引数と見なされます。
System.Runtime.CompilerServices.EnumeratorCancellation属性が複数のパラメーターに適用されている場合はエラーです。 コンパイラは、次の場合に警告を生成することがあります。
-
EnumeratorCancellation属性は、CancellationToken以外の型のパラメーターに適用されます。 - または、非同期反復子ではないメソッド (
EnumeratorCancellation) のパラメーターに属性が適用されている場合は、 - または、非同期列挙可能なインターフェイス (
EnumeratorCancellation) ではなく、非同期列挙子インターフェイス (§15.15.2) を返すメソッドのパラメーターに属性が適用されている場合。
このパラメーターを持つ属性がない場合、反復子はCancellationTokenのGetAsyncEnumerator引数にアクセスできません。
例:
GetStringsAsync()メソッドは非同期反復子です。 次の値を取得する作業を行う前に、取り消しトークンをチェックして、イテレーションを取り消す必要があるかどうかを判断します。 取り消しが要求された場合、それ以上のアクションは実行されません。public static async Task ExampleCombination() { var sourceOne = new CancellationTokenSource(); var sourceTwo = new CancellationTokenSource(); await using (IAsyncEnumerator<string> enumerator = GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token)) { while (await enumerator.MoveNextAsync()) { string number = enumerator.Current; if (number == "8") sourceOne.Cancel(); if (number == "5") sourceTwo.Cancel(); Console.WriteLine(number); } } } static async IAsyncEnumerable<string> GetStringsAsync( [EnumeratorCancellation] CancellationToken token) { for (int i = 0; i < 10; i++) { if (token.IsCancellationRequested) yield break; await Task.Delay(1000, token); yield return i.ToString(); } }終了サンプル
23.5.9 ModuleInitializer 属性
ModuleInitializer属性は、メソッドをモジュール初期化子としてマークするために使用されます。 このようなメソッドは、包含モジュールの初期化中に呼び出されます。 モジュールには複数の初期化子があり、これは実装で定義された順序で呼び出されます。
モジュール初期化子で許可されるコードに制限はありません。
モジュール初期化子には、次の特性があります。
-
method_modifier
static。 - parameter_listはありません。
-
voidのreturn_type。 - type_parameter_listはありません。
- type_parameter_listを持つclass_declaration内では宣言できません。
- 包含モジュールからアクセス可能である (つまり、アクセス修飾子
internalまたはpublic)。 - ローカル関数ではありません。
23.5.9.1 カスタム補間文字列式ハンドラー
23.5.9.1.1 カスタム ハンドラーの宣言
単純なメッセージ ロガーを実装する次のプログラムについて考えてみましょう。
using System;
public class Logger
{
public void LogMessage(string msg)
{
Console.WriteLine(msg);
}
}
public class Program
{
static void Main()
{
var logger = new Logger();
int val = 255;
logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}.");
}
}
生成される出力は次のとおりです。
val = { FF}; 2 * val = 510.
LogMessageの呼び出しでは、挿入文字列式引数のターゲットはパラメーター msgであり、型はstring。 そのため、 §12.8.3 によると、既定の補間文字列式ハンドラーが呼び出されます。 次のサブクラウス (§23.5.9.1.1) は、カスタム ハンドラーの使用方法を示しています。
上記のプログラムにカスタム処理を提供するには、 カスタム補間文字列式ハンドラー が必要です。 次に、カスタム ハンドラーが追加されたメッセージ ロガーを示します (既定のハンドラーと同様に動作しますが、カスタマイズのためのフックが提供されます)。
using System;
using System.Text;
using System.Runtime.CompilerServices;
[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
StringBuilder builder; // Storage for the built-up string
public LogInterpolatedStringHandler(int literalLength, int formattedCount)
{
builder = new StringBuilder(literalLength);
}
public void AppendLiteral(string s)
{
builder.Append(s);
}
public void AppendFormatted<T>(T t)
{
builder.Append(t?.ToString());
}
public void AppendFormatted<T>(T t, string format) where T : IFormattable
{
builder.Append(t?.ToString(format, null));
}
public void AppendFormatted<T>(T t, int alignment, string format)
where T : IFormattable
{
builder.Append(String.Format("{0" + "," + alignment + ":" + format + "}", t));
}
public override string ToString() => builder.ToString();
}
public class Logger
{
public void LogMessage(string msg)
{
Console.WriteLine(msg);
}
public void LogMessage(LogInterpolatedStringHandler builder)
{
Console.WriteLine(builder.ToString());
}
}
public class Program
{
static void Main()
{
var logger = new Logger();
int val = 255;
logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}.");
}
}
生成される出力は次のとおりです。
val = { FF}; 2 * val = 510.
属性 System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute を持つ型は、 適用可能な補間文字列ハンドラー型と言われます。
カスタム補間文字列式ハンドラーとして修飾するには、クラスまたは構造体型に次の特性が必要です。
- 属性
System.Runtime.CompilerServices.InterpolatedStringHandlerAttributeでマークされます。 - 最初の 2 つのパラメーターが型
intを持つ、アクセス可能なコンストラクターを用意します。 (他のパラメーターが続く場合があります。このパラメーターは、ハンドラーに対する情報の受け渡しに使用されます。これらは §23.5.9.1.3 で説明されています。省略可能な最終パラメーターを宣言して、ハンドラーが補間文字列を処理しないようにすることができます。これは §23.5.9.1.2) で説明されています。
コンパイラによって生成されたコードがコンストラクターを呼び出すと、最初のパラメーターは補間文字列式の補間文字列式セグメント (§12.8.3) の長さの合計に設定され、2 番目のパラメーターは補間の数に設定されます。 ( ($"val = {{{val,4:X}}}; 2 * val = {2 * val}."の場合、これらの値はそれぞれ 21 と 2 です)。
- シグネチャ
void AppendLiteral(string s)を使用してアクセス可能なメソッドを用意します。このメソッドは、1 つの挿入文字列式リテラル セグメントを処理するために呼び出されます。 -
AppendFormattedと呼ばれる、アクセス可能なオーバーロードされたメソッドのセットを用意します。そのうちの 1 つは、その補間の内容に基づいて 1 つの補間を処理するために呼び出されます。 署名は次のとおりです。-
void AppendFormatted<T>(T t)は、{2 * val}の場合と同様に、明示的な形式や配置を持たない補間を処理します。 -
void AppendFormatted<T>(T t, string format) where T : System.IFormattableは、{val:X4}の場合と同様に、明示的な形式を持つ補間を処理しますが、アラインメントは処理しません。 -
void AppendFormatted<T>(T t, int alignment, string format) where T : System.IFormattableは、{val,4:X}の場合と同様に、明示的な形式と配置を持つ補間を処理します。
-
- ビルドされた文字列を返すシグネチャ
override string ToString()を持つパブリック メソッドを使用します。
注:
AppendFormattedオーバーロードのいずれかを省略するのはコンパイル時エラーではありませんが、ハンドラーが最大限に堅牢である場合は、既定のハンドラーで認識されるすべての形式をサポートする必要があります。 注釈
LogMessageの新しいオーバーロードは、stringではなくカスタム ハンドラーを受け取り、そのハンドラーによって書式設定された文字列を取得します。 このようなオーバーロードが存在する場合、対応するハンドラーが存在し、補間された文字列式が定数 (§12.8.3) でない場合、コンパイラはハンドラーを受け取るハンドラーを呼び出すコードを生成します。 このような場合、コンパイラは次のコードを生成します。
- ハンドラー コンストラクターを呼び出す
- 挿入文字列式内の字句順
- 各補間文字列式セグメントを
AppendLiteral - 各補間を適切な
AppendFormattedメソッドに渡します。
- 各補間文字列式セグメントを
- は、挿入文字列式の値として最終的な文字列を返します。
-
LogMessageの本体を実行します。
23.5.9.1.2 カスタム ハンドラーの禁止
ハンドラー コンストラクターに out パラメーターである型 bool の最終的なパラメーターがある場合、そのコンストラクターが呼び出されると、そのパラメーターの値がテストされます。 true の場合、そのパラメーターが省略されたかのように動作します。 ただし、false の場合、補間された文字列式はそれ以上処理されません。つまり、ハンドラーは 禁止されます。 具体的には、補間式は評価されず、メソッド AppendLiteral および AppendFormatted は呼び出されません。
public LogInterpolatedStringHandler(int literalLength, int formattedCount,
out bool processString)
{
if (some_condition)
{
processString = false;
return;
}
else
{
processString = true;
// continue construction
}
}
注: 補間文字列式の補間には、( ++、 --、代入、一部のメソッド呼び出しの結果として) 副作用が含まれる場合があります。 ハンドラーが禁止されている場合、補間された文字列式の副作用は評価されません。 ハンドラーが禁止されていない場合は、挿入文字列式のすべての副作用が評価されます。
注釈
23.5.9.1.3 カスタム ハンドラーへの情報の受け渡し
カスタム ハンドラーに他の情報を渡し、そこから情報を受け取ると便利です。 これは、属性 System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentを使用して行われます。 メッセージ ロガー プログラムに対する次の新しいオーバーロードについて考えてみましょう。
public class Logger
{
// …
public void LogMessage(bool flag, int count,
[InterpolatedStringHandlerArgument("count","flag","")]
LogInterpolatedStringHandler builder)
{
// …
}
}
public ref struct LogInterpolatedStringHandler
{
// …
public LogInterpolatedStringHandler(int literalLength, int formattedCount,
int count, bool flag, Logger logger)
{
// …
}
}
属性 InterpolatedStringHandlerArgument はハンドラー パラメーターに適用され、ハンドラーに渡されるパラメーターの宣言に従います。 属性コンストラクター引数は、渡されるパラメーターに名前を付ける 0 個以上の文字列のコンマ区切りのリストと、その順序を指定する必要があります。 空の文字列は、ハンドラーの呼び出し元のインスタンスを指定します。 そのため、上記の属性コンストラクター呼び出しには、 "count","flag","" を含む一致するハンドラー コンストラクターが必要です。 属性コンストラクター引数リストが空の場合、動作は属性が省略されたかのように見えます。
ハンドラーを禁止できるように out bool パラメーターも宣言されている場合 (§23.5.9.1.2) は、そのパラメーターが最後のパラメーターになります。
23.6 相互運用のための属性
他の言語との相互運用のために、インデックス付きプロパティを使用してインデクサーを実装できます。 インデクサーに IndexerName 属性がない場合は、既定で 名前 Item が使用されます。
IndexerName 属性を使用すると、開発者はこの既定値をオーバーライドし、別の名前を指定できます。
例: 既定では、インデクサーの名前は
Itemです。 これは、次のようにオーバーライドされます。[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }これで、インデクサーの名前が
TheItemになります。終了サンプル
ECMA C# draft specification