15 クラス
15.1 全般
クラスは、データ メンバー (定数とフィールド)、関数メンバー (メソッド、プロパティ、イベント、インデクサー、演算子、インスタンス コンストラクター、ファイナライザー、静的コンストラクター)、および入れ子になった型を含むデータ構造です。 クラス型は継承をサポートします。これは、 の派生クラス が ベース クラスを拡張して特殊化できるメカニズムです。
15.2 クラス宣言
15.2.1 全般
class_declarationは、新しいクラスを宣言するtype_declaration (§14.7) です。
class_declaration
: attributes? class_modifier* 'partial'? 'class' identifier
type_parameter_list? class_base? type_parameter_constraints_clause*
class_body ';'?
;
class_declarationは、オプションの attributes (§22) のセットで構成され、その後にオプションの一連のclass_modifier (§15.2.2) が続きます。 2)、省略可能なpartial
修飾子 (§15.2.7)、その後にキーワード class
とクラスに名前を付けた identifier が続きます。 省略可能なtype_parameter_list (§15.2.3) の後に省略可能なclass_base仕様 (§15.2.4)、オプションの一連のtype_parameter_constraints_clause (§15.2.5)、その後にclass_body (§15.2.6)、必要に応じてセミコロンが続きます。
クラス宣言は、type_parameter_listも提供しない限り、type_parameter_constraints_clauseを提供しません。
type_parameter_listを提供するクラス宣言は、ジェネリック クラス宣言です。 さらに、ジェネリック クラス宣言またはジェネリック構造体宣言内に入れ子になったクラスは、それ自体がジェネリック クラス宣言です。これは、包含型の型引数を指定して構築型 (§8.4) を作成する必要があるためです。
15.2.2 クラス修飾子
15.2.2.1 全般
class_declarationには、必要に応じてクラス修飾子のシーケンスを含めることができます。
class_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'abstract'
| 'sealed'
| 'static'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
クラス宣言で同じ修飾子が複数回出現するのはコンパイル時エラーです。
入れ子になったクラスでは、 new
修飾子を使用できます。 §15.3.5 で説明されているように、クラスが同じ名前で継承されたメンバーを非表示にすることを指定。 入れ子になったクラス宣言ではないクラス宣言に new
修飾子が表示されるコンパイル時エラーです。
public
、protected
、internal
、およびprivate
修飾子は、クラスのアクセシビリティを制御します。 クラス宣言が発生するコンテキストによっては、これらの修飾子の一部が許可されない場合があります (§7.5.2)。
部分型宣言 (§15.2.7) にアクセシビリティ仕様 ( public
、 protected
、 internal
、および private
修飾子を使用) が含まれている場合、その仕様はアクセシビリティ仕様を含む他のすべての部分と一致するものとします。 部分型の一部にアクセシビリティ仕様が含まれている場合、その型には適切な既定のアクセシビリティ (§7.5.2) が付与されます。
次のサブクラウスでは、 abstract
、 sealed
、および static
修飾子について説明します。
15.2.2.2 抽象クラス
abstract
修飾子は、クラスが不完全であり、基底クラスとしてのみ使用されることを示すために使用されます。 abstract クラスは、次の点で非抽象クラスとは異なります。
- 抽象クラスを直接インスタンス化することはできません。また、抽象クラスで
new
演算子を使用するのはコンパイル時エラーです。 コンパイル時の型が抽象である変数と値を持つことはできますが、このような変数と値は必ずしもnull
であるか、抽象型から派生した非抽象クラスのインスタンスへの参照を含みます。 - 抽象クラスは、抽象メンバーを含む (ただし必須ではない) 場合に許可されます。
- 抽象クラスをシールすることはできません。
非抽象クラスが抽象クラスから派生した場合、非抽象クラスには、継承されたすべての抽象メンバーの実際の実装が含まれるため、それらの抽象メンバーがオーバーライドされます。
例: 次のコード内
abstract class A { public abstract void F(); } abstract class B : A { public void G() {} } class C : B { public override void F() { // Actual implementation of F } }
抽象クラス
A
では、抽象メソッドのF
が導入されています。 クラスB
では、G
追加のメソッドが導入されていますが、F
の実装を提供していないため、B
は抽象として宣言されます。 クラスC
は、F
をオーバーライドし、実際の実装を提供します。C
には抽象メンバーがないため、C
は非抽象にすることが許可されます (ただし、必須ではありません)。end の例
クラスの部分型宣言 (§15.2.7) の 1 つ以上の部分に abstract
修飾子が含まれている場合、クラスは抽象です。 それ以外の場合、クラスは非抽象です。
15.2.2.3 シールクラス
sealed
修飾子は、クラスからの派生を防ぐために使用されます。 シールクラスが別のクラスの基底クラスとして指定されている場合、コンパイル時エラーが発生します。
シール クラスを抽象クラスにすることはできません。
注:
sealed
修飾子は、主に意図しない派生を防ぐために使用されますが、特定の実行時の最適化も可能になります。 特に、シール クラスには派生クラスが存在しないことがわかっているため、シール クラス インスタンスの仮想関数メンバーの呼び出しを非仮想呼び出しに変換できます。 end note
クラスの部分型宣言の 1 つ以上の部分 (§15.2.7) に sealed
修飾子が含まれている場合、クラスはシールされます。 それ以外の場合、クラスは封印解除されます。
15.2.2.4 静的クラス
15.2.2.4.1 全般
static
修飾子は、宣言されているクラスを静的クラスとしてマークするために使用されます。 静的クラスはインスタンス化されず、型として使用されず、静的メンバーのみを含む必要があります。 拡張メソッドの宣言を含めることができるのは静的クラスのみです (§15.6.10)。
静的クラス宣言には、次の制限があります。
- 静的クラスには、
sealed
またはabstract
修飾子を含めてはならない。 (ただし、静的クラスはインスタンス化または派生できないため、シールと抽象の両方であるかのように動作します)。 - 静的クラスには、 class_base 仕様 (§15.2.4) を含めず、基底クラスまたは実装されているインターフェイスの一覧を明示的に指定することはできません。 静的クラスは、型
object
から暗黙的に継承します。 - 静的クラスには、静的メンバー (§15.3.8) のみを含める必要があります。
注: すべての定数と入れ子になった型は、静的メンバーとして分類されます。 end note
- 静的クラスには、
protected
、private protected
、または宣言されたアクセシビリティprotected internal
メンバーは含まれません。
これらの制限のいずれかに違反するのはコンパイル時エラーです。
静的クラスにはインスタンス コンストラクターがありません。 静的クラスでインスタンス コンストラクターを宣言することはできず、静的クラスに既定のインスタンス コンストラクター (§15.11.5) は提供されません。
静的クラスのメンバーは自動的に静的ではなく、メンバー宣言には明示的に static
修飾子を含める必要があります (定数と入れ子になった型を除く)。 静的外部クラス内でクラスが入れ子になっている場合、入れ子になったクラスは、 static
修飾子を明示的に含まない限り、静的クラスではありません。
クラスの部分型宣言 (§15.2.7) の 1 つ以上の部分に static
修飾子が含まれている場合、クラスは静的です。 それ以外の場合、クラスは静的ではありません。
15.2.2.4.2 静的クラス型の参照
namespace_or_type_name (§7.8) は、
- namespace_or_type_nameは、フォーム
T.I
のnamespace_or_type_name内のT
です。 - namespace_or_type-name は、フォーム
typeof(T)
のtypeof_expression (§12.8.18) のT
です。
primary_expression (§12.8) は、
- primary_expressionは、フォーム
E.I
のmember_access (§12.8.7) のE
です。
その他のコンテキストでは、静的クラスを参照するのはコンパイル時エラーです。
注: たとえば、静的クラスが基底クラス、メンバーの構成要素の型 (§15.3.7)、ジェネリック型引数、または型パラメーター制約として使用される場合のエラーです。 同様に、静的クラスは、配列型、新しい式、キャスト式、is 式、as 式、
sizeof
式、または既定値の式では使用できません。 end note
15.2.3 型パラメーター
型パラメーターは、構築された型を作成するために指定された型引数のプレースホルダーを表す単純な識別子です。 constrast では、型引数 (§8.4.2) は、構築された型の作成時に型パラメーターに置き換える型です。
type_parameter_list
: '<' type_parameters '>'
;
type_parameters
: attributes? type_parameter
| type_parameters ',' attributes? type_parameter
;
type_parameter は、 §8.5 で定義されています。
クラス宣言の各型パラメーターは、そのクラスの宣言空間 (§7.3) に名前を定義します。 したがって、そのクラスの別の型パラメーターや、そのクラスで宣言されたメンバーと同じ名前を持つことはできません。 型パラメーターは、型自体と同じ名前を持つことはできません。
2 つの部分ジェネリック型宣言 (同じプログラム内) は、同じ完全修飾名 (型パラメーターの数に対して generic_dimension_specifier (§12.8.18) を含む) (§7.8.3) を持つ場合、同じ非連結ジェネリック型に影響します。 このような 2 つの部分型宣言では、各型パラメーターに同じ名前を順番に指定する必要があります。
15.2.4 クラスの基本仕様
15.2.4.1 全般
クラス宣言には、クラスの直接基底クラスと、クラスによって直接実装されるインターフェイス (§18) を定義するclass_base仕様を含めることができます。
class_base
: ':' class_type
| ':' interface_type_list
| ':' class_type ',' interface_type_list
;
interface_type_list
: interface_type (',' interface_type)*
;
15.2.4.2 基本クラス
class_baseにclass_typeが含まれている場合は、宣言するクラスの直接基底クラスを指定します。 非部分クラス宣言に class_baseがない場合、または class_base がインターフェイス型のみを一覧表示する場合、ダイレクト 基底クラスは object
と見なされます。 部分クラス宣言に基底クラス仕様が含まれている場合、その基底クラス仕様は、基底クラス仕様を含む部分型の他のすべての部分と同じ型を参照する必要があります。 部分クラスの一部に基底クラスの仕様が含まれている場合、基底クラスは object
。 クラスは、 §15.3.4 で説明されているように、直接基底クラスからメンバーを継承します。
例: 次のコード内
class A {} class B : A {}
クラス
A
はB
の直接基底クラスと言われ、B
はA
から派生していると言われます。A
は直接基底クラスを明示的に指定しないため、その直接基底クラスは暗黙的にobject
。end の例
ジェネリック型宣言内で宣言された入れ子になった型 (§15.3.9.7) を含む、構築されたクラス型の場合、ジェネリック クラス宣言で基底クラスが指定されている場合、構築された型の基底クラスは、基底クラス宣言の各 type_parameter に対して、構築された型の対応する type_argument を置き換えることによって取得されます。
例: ジェネリック クラス宣言を指定する
class B<U,V> {...} class G<T> : B<string,T[]> {...}
構築された型
G<int>
の基底クラスはB<string,int[]>
。end の例
クラス宣言で指定される基底クラスは、構築されたクラス型 (§8.4) にすることができます。 基底クラスは独自の型パラメーター (§8.5) にすることはできませんが、スコープ内の型パラメーターを含めることができます。
例:
class Base<T> {} // Valid, non-constructed class with constructed base class class Extend1 : Base<int> {} // Error, type parameter used as base class class Extend2<V> : V {} // Valid, type parameter used as type argument for base class class Extend3<V> : Base<V> {}
end の例
クラス型の直接基底クラスは、少なくともクラス型自体と同じくらいアクセス可能である必要があります (§7.5.5)。 たとえば、パブリック クラスがプライベート クラスまたは内部クラスから派生するコンパイル時エラーです。
クラス型の直接基底クラスは、 System.Array
、 System.Delegate
、 System.Enum
、 System.ValueType
、または dynamic
型のいずれにもすることはできません。 さらに、ジェネリック クラス宣言では、直接または間接的な基底クラスとして System.Attribute
を使用しないでください (§22.2.1)。
クラス B
の直接基底クラス仕様A
の意味を決定する際に、B
の直接基底クラスは一時的にobject
と見なされるため、基底クラスの仕様の意味がそれ自体に再帰的に依存できなくなります。
例: 次
class X<T> { public class Y{} } class Z : X<Z.Y> {}
基底クラス仕様では
Z
の直接基底クラスX<Z.Y>
object
と見なされるため、(§7.8 の規則では)Z
はメンバーY
を持たないと見なされるため、エラーになります。end の例
クラスの基底クラスは、直接基底クラスとその基底クラスです。 言い換えると、基底クラスのセットは、直接基底クラスリレーションシップの推移的なクロージャです。
例: 次の例を参照してください。
class A {...} class B<T> : A {...} class C<T> : B<IComparable<T>> {...} class D<T> : C<T[]> {...}
D<int>
の基底クラスは、C<int[]>
、B<IComparable<int[]>>
、A
、およびobject
です。end の例
クラス object
を除き、すべてのクラスには 1 つの直接基底クラスがあります。 object
クラスには直接基底クラスがなく、他のすべてのクラスの最終的な基底クラスです。
クラスがそれ自体に依存するのはコンパイル時エラーです。 この規則の目的上、クラス 間接的に依存し その直接基底クラス (存在する場合) と 間接的に依存します 入れ子になっている最も近い外側のクラス (存在する場合)。 この定義を考えると、クラスが依存するクラスの完全なセットは、 の推移的な終了が 関係に依存します。
例: 例
class A : A {}
クラスがそれ自体に依存しているため、エラーです。 同様に、例
class A : B {} class B : C {} class C : A {}
は、クラスが循環的に自身に依存しているため、エラーになります。 最後に、次の例を示します。
class A : B.C {} class B : A { public class C {} }
A は
A
に循環して依存するB
(すぐに外側のクラス) に依存するB.C
(その直接基底クラス) に依存するため、コンパイル時エラーが発生します。end の例
クラスは、その中で入れ子になっているクラスに依存しません。
例: 次のコード内
class A { class B : A {} }
B
はA
に依存します (A
は直接基底クラスと直ちに外側のクラスの両方であるため)、A
はB
に依存しません (B
は基底クラスでも、A
の外側のクラスでもありません)。 したがって、この例は有効です。end の例
シールされたクラスから派生することはできません。
例: 次のコード内
sealed class A {} class B : A {} // Error, cannot derive from a sealed class
クラス
B
は、シールされたクラスA
から派生しようとするため、エラーが発生しています。end の例
15.2.4.3 インターフェイスの実装
class_base仕様には、インターフェイス型の一覧が含まれる場合があります。その場合、クラスは特定のインターフェイス型を実装すると言われます。 ジェネリック型宣言 (§15.3.9.7 内で宣言された入れ子になった型を含む、構築されたクラス型の場合、実装された各インターフェイス型は、指定されたインターフェイス内の各 type_parameter に対して、構築された型の対応する type_argument を置き換えることによって取得されます。
複数の部分で宣言された型のインターフェイスのセット (§15.2.7) は、各部分で指定されたインターフェイスの和集合です。 特定のインターフェイスは各パーツで 1 回だけ名前を付けることができますが、複数の部分で同じ基本インターフェイスに名前を付けることができます。 特定のインターフェイスの各メンバーの実装は 1 つだけです。
例: 次の例を参照してください。
partial class C : IA, IB {...} partial class C : IC {...} partial class C : IA, IB {...}
クラス
C
の基本インターフェイスのセットは、IA
、IB
、およびIC
です。end の例
通常、各部分は、その部分で宣言されたインターフェイスの実装を提供します。ただし、これは要件ではありません。 パーツは、別の部分で宣言されたインターフェイスの実装を提供できます。
例:
partial class X { int IComparable.CompareTo(object o) {...} } partial class X : IComparable { ... }
end の例
クラス宣言で指定された基本インターフェイスは、インターフェイス型 (§8.4、 §18.2) で構築できます。 基本インターフェイスは単独では型パラメーターにすることはできませんが、スコープ内の型パラメーターを含めることができます。
例: 次のコードは、クラスが構築された型を実装および拡張する方法を示しています。
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
end の例
インターフェイスの実装については、 §18.6 で詳しく説明します。
15.2.5 型パラメーターの制約
ジェネリック型とメソッドの宣言では、必要に応じて、 type_parameter_constraints_clauseを含めることで、型パラメーターの制約を指定できます。
type_parameter_constraints_clauses
: type_parameter_constraints_clause
| type_parameter_constraints_clauses type_parameter_constraints_clause
;
type_parameter_constraints_clause
: 'where' type_parameter ':' type_parameter_constraints
;
type_parameter_constraints
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
| secondary_constraints (',' constructor_constraint)?
| constructor_constraint
;
primary_constraint
: class_type nullable_type_annotation?
| 'class' nullable_type_annotation?
| 'struct'
| 'notnull'
| 'unmanaged'
;
secondary_constraint
: interface_type nullable_type_annotation?
| type_parameter nullable_type_annotation?
;
secondary_constraints
: secondary_constraint (',' secondary_constraint)*
;
constructor_constraint
: 'new' '(' ')'
;
各 type_parameter_constraints_clause は、トークン where
、型パラメーターの名前、コロン、その型パラメーターの制約の一覧で構成されます。 型パラメーターごとに最大 1 つの where
句を使用でき、 where
句は任意の順序で一覧表示できます。 プロパティ アクセサーの get
トークンや set
トークンと同様に、 where
トークンはキーワードではありません。
where
句で指定される制約の一覧には、1 つのプライマリ制約、1 つ以上のセカンダリ制約、コンストラクター制約 、new()
の順に、次のいずれかのコンポーネントを含めることができます。
プライマリ制約には、クラス型、参照型制約 class
値型制約 struct
null 制約 notnull
または管理されていない型制約を指定できます unmanaged
。 クラス型と参照型制約には、 nullable_type_annotationを含めることができます。
セカンダリ制約には、 interface_type または type_parameter、必要に応じて nullable_type_annotationを指定できます。 nullable_type_annotatione* は、型引数が、制約を満たす null 非許容参照型に対応する null 許容参照型であることが許可されていることを示します。
参照型制約は、型パラメーターに使用される型引数が参照型であることを指定します。 すべてのクラス型、インターフェイス型、デリゲート型、配列型、および参照型であることが知られている型パラメーター (以下で定義) は、この制約を満たしています。
クラス型、参照型制約、およびセカンダリ制約には、null 許容型注釈を含めることができます。 型パラメーターにこの注釈が存在するかどうかは、型引数に対する null 許容の期待値を示します。
- 制約に null 許容型注釈が含まれていない場合、型引数は null 非許容参照型であると想定されます。 型引数が null 許容参照型の場合、コンパイラは警告を発行することがあります。
- 制約に null 許容型注釈が含まれている場合、null 非許容参照型と null 許容参照型の両方で制約が満たされます。
型引数の null 値許容は、型パラメーターの null 許容値と一致する必要はありません。 型パラメーターの null 許容が型引数の null 許容値と一致しない場合、コンパイラは警告を発行することがあります。
注: 型引数が null 許容参照型であることを指定するには、null 許容型注釈を制約として追加しないでください (
T : class
またはT : BaseClass
を使用します)、ジェネリック宣言全体でT?
を使用して、型引数の対応する null 許容参照型を示します。 end note
null 許容型の注釈 ( ?
) は、制約のない型引数では使用できません。
型引数が null 許容参照型C?
である場合にT
型パラメーターの場合、T?
のインスタンスはC??
ではなくC?
として解釈されます。
例: 次の例は、型引数の null 許容が型パラメーターの宣言の null 許容に与える影響を示しています。
public class C { } public static class Extensions { public static void M<T>(this T? arg) where T : notnull { } } public class Test { public void M() { C? mightBeNull = new C(); C notNull = new C(); int number = 5; int? missing = null; mightBeNull.M(); // arg is C? notNull.M(); // arg is C? number.M(); // arg is int? missing.M(); // arg is int? } }
型引数が null 非許容型の場合、
?
型注釈は、パラメーターが対応する null 許容型であることを示します。 型引数が既に null 許容参照型の場合、パラメーターは同じ null 許容型です。end の例
null 制約は、型パラメーターに使用される型引数を null 非許容値型または null 非許容参照型にすることを指定します。 null 非許容値型または null 非許容参照型ではない型引数は許可されますが、コンパイラによって診断警告が生成される場合があります。
値型制約は、型パラメーターに使用される型引数が null 非許容値型であることを指定します。 null 非許容構造体型、列挙型、および値型制約を持つ型パラメーターはすべて、この制約を満たしています。 値型として分類されますが、null 許容値型 (§8.3.12) は値型の制約を満たしていません。 値型制約を持つ型パラメーターには constructor_constraintも含まれませんが、 constructor_constraintを持つ別の型パラメーターの型引数として使用できます。
注:
System.Nullable<T>
型は、T
の null 非許容値型制約を指定します。 したがって、T??
およびNullable<Nullable<T>>
のフォームの再帰的に構築された型は禁止されています。 end note
unmanaged
はキーワードではないため、アンマネージ制約primary_constraintでは、class_typeでは常に構文的にあいまいです。 互換性上の理由から、名前unmanaged
の名前参照 (§12.8.4) が成功した場合、class_type
として扱われます。 それ以外の場合は、アンマネージ制約として扱われます。
アンマネージ型制約は、型パラメーターに使用される型引数が null 非許容アンマネージド型 (§8.8) であることを指定します。
ポインター型を型引数にすることは決して許可されず、アンマネージ型であっても、型制約を満たしません。
制約がクラス型、インターフェイス型、または型パラメーターである場合、その型は、その型パラメーターに使用されるすべての型引数がサポートする最小の "基本型" を指定します。 構築された型またはジェネリック メソッドが使用されるたびに、コンパイル時に型パラメーターの制約に対して型引数がチェックされます。 指定する型引数は、 §8.4.5 で説明されている条件を満たす必要があります。
class_type制約は、次の規則を満たす必要があります。
- 型はクラス型でなければなりません。
- 型は
sealed
されません。 - 型は、
System.Array
またはSystem.ValueType
の型ではありません。 - 型は
object
されません。 - 特定の型パラメーターに対して最大 1 つの制約がクラス型である可能性があります。
interface_type制約として指定された型は、次の規則を満たす必要があります。
- 型はインターフェイス型でなければなりません。
- 指定した
where
句で、型を複数回指定することはできません。
どちらの場合も、制約には、構築された型の一部として、関連付けられている型またはメソッド宣言のいずれかの型パラメーターが含まれる場合があり、宣言されている型が含まれる場合があります。
型パラメーター制約として指定されたクラスまたはインターフェイス型は、宣言されているジェネリック型またはメソッドと同じアクセス可能な (§7.5.5) 以上である必要があります。
type_parameter制約として指定された型は、次の規則を満たす必要があります。
- 型は型パラメーターでなければなりません。
- 指定した
where
句で、型を複数回指定することはできません。
さらに、型パラメーターの依存関係グラフにはサイクルはありません。依存関係は、次によって定義される推移的な関係です。
- 型パラメーター
T
が型パラメーターS
の制約として使用されている場合は、S
に依存T
。 - 型パラメーター
S
が型パラメーターのT
に依存し、T
が型パラメーターに依存U
場合は、S
依存しますU
。
この関係を考えると、型パラメーターがそれ自体に依存するのはコンパイル時エラーです (直接または間接的)。
すべての制約は、依存型パラメーター間で一貫している必要があります。 型パラメーター S
が型パラメーターの T
に依存している場合は、次のようになります。
T
には、値型の制約はありません。 それ以外の場合、T
は効果的にシールされるため、S
はT
と同じ型に強制されるため、2 つの型パラメーターが不要になります。S
に値型制約がある場合、T
にはclass_type制約は適用されません。S
にclass_type制約A
があり、T
にclass_type制約B
がある場合は、A
からB
への ID 変換または暗黙的な参照変換、またはB
からA
への暗黙的な参照変換が必要です。S
型パラメーターU
にも依存し、U
にclass_type制約A
があり、T
にclass_type制約B
がある場合は、A
からB
への ID 変換または暗黙的な参照変換、またはB
からA
への暗黙的な参照変換が必要です。
S
が値型制約を持ち、T
が参照型制約を持つことは有効です。 実質的に、これにより、System.Object
、System.ValueType
、System.Enum
、および任意のインターフェイス型にT
が制限されます。
型パラメーターの where
句にコンストラクター制約 (フォーム new()
) が含まれている場合は、 new
演算子を使用して型のインスタンスを作成できます (§12.8.17.2)。 コンストラクター制約を持つ型パラメーターに使用される型引数は、値型、パブリック パラメーターなしのコンストラクターを持つ非抽象クラス、または値型制約またはコンストラクター制約を持つ型パラメーターになります。
struct
またはunmanaged
のprimary_constraintを持つtype_parameter_constraintsがconstructor_constraintを持つことは、コンパイル時エラーです。
例: 制約の例を次に示します。
interface IPrintable { void Print(); } interface IComparable<T> { int CompareTo(T value); } interface IKeyProvider<T> { T GetKey(); } class Printer<T> where T : IPrintable {...} class SortedList<T> where T : IComparable<T> {...} class Dictionary<K,V> where K : IComparable<K> where V : IPrintable, IKeyProvider<K>, new() { ... }
次の例では、型パラメーターの依存関係グラフで循環性が発生するため、エラーが発生しています。
class Circular<S,T> where S: T where T: S // Error, circularity in dependency graph { ... }
次の例は、その他の無効な状況を示しています。
class Sealed<S,T> where S : T where T : struct // Error, `T` is sealed { ... } class A {...} class B {...} class Incompat<S,T> where S : A, T where T : B // Error, incompatible class-type constraints { ... } class StructWithClass<S,T,U> where S : struct, T where T : U where U : A // Error, A incompatible with struct { ... }
end の例
C
型の動的消去は、次のように構築Cₓ
型です。
C
が入れ子になった型Outer.Inner
場合、Cₓ
は入れ子になった型Outerₓ.Innerₓ
になります。C
Cₓ
型引数を持つ構築型G<A¹, ..., Aⁿ>
A¹, ..., Aⁿ
場合、Cₓ
は構築された型G<A¹ₓ, ..., Aⁿₓ>
です。C
が配列型E[]
場合、Cₓ
は配列型Eₓ[]
です。C
が動的な場合、Cₓ
はobject
。- それ以外の場合、
Cₓ
はC
です。
型パラメーター T
の効果のない基底クラスは次のように定義されます。
R
次のような型のセットにしましょう。
- 型パラメーターである
T
の各制約について、R
は有効な基底クラスを含みます。 - 構造体型である
T
の制約ごとに、R
にはSystem.ValueType
が含まれます。 - 列挙型である
T
の制約ごとに、R
にはSystem.Enum
が含まれます。 - デリゲート型である
T
の各制約について、R
には動的消去が含まれます。 - 配列型である
T
の各制約について、R
にはSystem.Array
が含まれます。 - クラス型である
T
の各制約について、R
は動的消去を含みます。
THEN
T
に値型制約がある場合、その有効な基底クラスはSystem.ValueType
。- それ以外の場合、
R
が空の場合、有効な基底クラスはobject
。 - それ以外の場合、
T
の有効な基底クラスは、setR
の最も包含型 (§10.5.3) です。 セットに包含型がない場合、T
の有効な基底クラスはobject
。 整合性規則により、最も包括的な型が確実に存在します。
型パラメーターが、基本メソッドから制約を継承するメソッド型パラメーターの場合、有効な基底クラスは型の置換後に計算されます。
これらの規則により、有効な基底クラスが常に class_typeになります。
型パラメーター T
の効果のないインターフェイス セットは次のように定義されます。
T
にsecondary_constraintsがない場合、その有効なインターフェイス セットは空です。T
にinterface_type制約があるが、type_parameter制約がない場合、その有効なインターフェイス セットは、そのinterface_type制約の動的消去のセットです。T
にinterface_type制約がなく、type_parameter制約がある場合、その有効なインターフェイス セットは、そのtype_parameter制約の有効なインターフェイス セットの和集合になります。T
にinterface_type制約とtype_parameter制約の両方がある場合、その有効なインターフェイス セットは、interface_type制約の動的消去のセットと、そのtype_parameter制約の有効なインターフェイス セットの和集合です。
型パラメーター 参照型として指定 参照型制約がある場合、またはその有効な基底クラスが object
または System.ValueType
されていない場合です。 型パラメーター null 非許容参照型である必要があります 参照型であることがわかっていて、null 非許容参照型制約がある場合です。
制約付き型パラメーター型の値を使用して、制約によって暗黙的に指定されたインスタンス メンバーにアクセスできます。
例: 次の例を参照してください。
interface IPrintable { void Print(); } class Printer<T> where T : IPrintable { void PrintOne(T x) => x.Print(); }
T
は常にIPrintable
を実装するように制限されているため、IPrintable
のメソッドはx
で直接呼び出すことができます。end の例
部分ジェネリック型宣言に制約が含まれている場合、制約は制約を含む他のすべての部分と一致する必要があります。 具体的には、制約を含む各部分には、同じ型パラメーターのセットに対する制約があり、各型パラメーターに対して、プライマリ、セカンダリ、およびコンストラクターの制約のセットは同等である必要があります。 同じメンバーが含まれている場合、2 つの制約セットは同等です。 部分ジェネリック型の一部が型パラメーター制約を指定していない場合、型パラメーターは制約なしと見なされます。
例:
partial class Map<K,V> where K : IComparable<K> where V : IKeyProvider<K>, new() { ... } partial class Map<K,V> where V : IKeyProvider<K>, new() where K : IComparable<K> { ... } partial class Map<K,V> { ... }
制約を含む部分 (最初の 2 つ) は、同じ型パラメーターのセットに対して同じプライマリ制約、セカンダリ 制約、コンストラクター制約のセットを効果的に指定するため、正しいです。
end の例
15.2.6 クラス本体
クラスの class_body は、そのクラスのメンバーを定義します。
class_body
: '{' class_member_declaration* '}'
;
15.2.7 部分宣言
修飾子 partial
は、クラス、構造体、またはインターフェイス型を複数の部分で定義するときに使用されます。 partial
修飾子はコンテキスト キーワード (§6.4.4) であり、キーワードclass
、struct
、またはinterface
の直前にのみ特別な意味を持ちます。
partial 型宣言の各部分には、partial
修飾子を含める必要があり、同じ名前空間で宣言するか、他の部分と同じ型を含む必要があります。 partial
修飾子は、型宣言の追加の部分が他の場所に存在する可能性があることを示しますが、このような追加部分の存在は必須ではありません。partial
修飾子を含めるのは型の唯一の宣言で有効です。 基底クラスまたは実装されたインターフェイスを含める部分型の宣言は 1 つだけ有効です。 ただし、基底クラスまたは実装されたインターフェイスのすべての宣言は、指定された型引数の null 許容を含め、一致する必要があります。
部分型のすべての部分は、コンパイル時にマージできるように一緒にコンパイルする必要があります。 部分型では、コンパイル済みの型を拡張することはできません。
入れ子になった型は、 partial
修飾子を使用して複数の部分で宣言できます。 通常、包含型は partial
を使用して宣言され、入れ子になった型の各部分は、包含型の異なる部分で宣言されます。
例: 次の部分クラスは、異なるコンパイル 単位に存在する 2 つの部分で実装されます。 最初の部分はデータベース マッピング ツールによって生成されたマシンですが、2 番目の部分は手動で作成されます。
public partial class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } } // File: Customer2.cs public partial class Customer { public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
上記の 2 つの部分が一緒にコンパイルされると、結果のコードは、クラスが 1 つのユニットとして記述されたかのように動作します。
public class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
end の例
部分宣言のさまざまな部分の型パラメーターまたは型パラメーターで指定された属性の処理については、 §22.3 で説明します。
15.3 クラス メンバー
15.3.1 全般
クラスのメンバーは、その 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
;
クラスのメンバーは、次のカテゴリに分類されます。
- クラスに関連付けられている定数値を表す定数 (§15.4)。
- クラスの変数であるフィールド (§15.5)。
- クラスで実行できる計算とアクションを実装するメソッド (§15.6)。
- プロパティ。名前付き特性と、それらの特性の読み取りと書き込みに関連するアクションを定義します (§15.7)。
- イベント。クラスによって生成できる通知を定義します (§15.8)。
- インデクサー。クラスのインスタンスは、配列と同じ方法 (構文的に) インデックスを作成できます (§15.9)。
- 演算子。クラスのインスタンスに適用できる式演算子を定義します (§15.10)。
- クラスのインスタンスを初期化するために必要なアクションを実装するインスタンス コンストラクター (§15.11)
- ファイナライザー。クラスのインスタンスが完全に破棄される前に実行されるアクションを実装します (§15.13)。
- クラス自体を初期化するために必要なアクションを実装する静的コンストラクター (§15.12)。
- クラスに対してローカルな型を表す型 (§14.7)。
class_declarationによって新しい宣言空間 (§7.3) が作成され、type_parameterとclass_member_declarationがすぐにclass_declarationに含まれる新しいメンバーがこの宣言空間に導入されます。 class_member_declarationには、次の規則が適用されます。
インスタンス コンストラクター、ファイナライザー、および静的コンストラクターは、すぐに囲むクラスと同じ名前を持つ必要があります。 他のすべてのメンバーは、すぐに囲むクラスの名前とは異なる名前を持つ必要があります。
クラス宣言の type_parameter_list の型パラメーターの名前は、同じ type_parameter_list 内の他のすべての型パラメーターの名前とは異なり、クラスの名前とクラスのすべてのメンバーの名前とは異なります。
型の名前は、同じクラスで宣言されているすべての型以外のメンバーの名前とは異なります。 2 つ以上の型宣言が同じ完全修飾名を共有する場合、宣言には
partial
修飾子 (§15.2.7) があり、これらの宣言を組み合わせて 1 つの型を定義します。
注: 型宣言の完全修飾名は型パラメーターの数をエンコードするため、型パラメーターの数が異なる限り、2 つの異なる型は同じ名前を共有できます。 end note
定数、フィールド、プロパティ、またはイベントの名前は、同じクラスで宣言されている他のすべてのメンバーの名前とは異なります。
メソッドの名前は、同じクラスで宣言されている他のすべての非メソッドの名前とは異なります。 さらに、メソッドのシグネチャ (§7.6) は、同じクラスで宣言されている他のすべてのメソッドのシグネチャと異なる必要があり、同じクラスで宣言されている 2 つのメソッドには、
in
、out
、およびref
によってのみ異なるシグネチャは含まれません。インスタンス コンストラクターのシグネチャは、同じクラスで宣言されている他のすべてのインスタンス コンストラクターのシグネチャとは異なります。また、同じクラスで宣言された 2 つのコンストラクターには、
ref
とout
によってのみ異なるシグネチャは含まれません。インデクサーのシグネチャは、同じクラスで宣言されている他のすべてのインデクサーのシグネチャとは異なる必要があります。
演算子のシグネチャは、同じクラスで宣言されている他のすべての演算子のシグネチャとは異なる必要があります。
クラスの継承されたメンバー (§15.3.4) は、クラスの宣言空間の一部ではありません。
注: したがって、派生クラスは、継承されたメンバーと同じ名前またはシグネチャを持つメンバーを宣言できます (継承されたメンバーは実質的に非表示になります)。 end note
複数の部分で宣言された型のメンバーのセット (§15.2.7) は、各部分で宣言されたメンバーの和集合です。 型宣言のすべての部分の本体は同じ宣言空間 (§7.3) を共有し、各メンバーのスコープ (§7.7) はすべての部分の本体まで拡張します。 メンバーのアクセシビリティ ドメインには、常に外側の型のすべての部分が含まれます。ある部分で宣言されたプライベート メンバーは、別の部分から自由にアクセスできます。 そのメンバーが partial
修飾子を持つ型でない限り、型の複数の部分で同じメンバーを宣言するのはコンパイル時エラーです。
例:
partial class A { int x; // Error, cannot declare x more than once partial class Inner // Ok, Inner is a partial type { int y; } } partial class A { int x; // Error, cannot declare x more than once partial class Inner // Ok, Inner is a partial type { int z; } }
end の例
フィールドの初期化順序は C# コード内で重要な場合があり、 §15.5.6.1 で定義されているように、いくつかの保証が提供。 それ以外の場合、型内のメンバーの順序はほとんど重要ではありませんが、他の言語や環境とやり取りする場合は重要な場合があります。 このような場合、複数の部分で宣言された型内のメンバーの順序は未定義です。
15.3.2 インスタンスの種類
各クラス宣言には、 instance 型が関連付けられています。 ジェネリック クラス宣言の場合、インスタンス型は、型宣言から構築された型 (§8.4) を作成することによって形成され、指定された各型引数が対応する型パラメーターになります。 インスタンス型は型パラメーターを使用するため、型パラメーターがスコープ内にある場合にのみ使用できます。つまり、クラス宣言内です。 インスタンス型は、クラス宣言内で記述されたコードの this
の型です。 非ジェネリック クラスの場合、インスタンス型は単に宣言されたクラスです。
例: インスタンス型と共にいくつかのクラス宣言を次に示します。
class A<T> // instance type: A<T> { class B {} // instance type: A<T>.B class C<U> {} // instance type: A<T>.C<U> } class D {} // instance type: D
end の例
15.3.3 構築された型のメンバー
構築された型の非継承メンバーは、メンバー宣言内の各 type_parameter に対して、構築された型の対応する type_argument を置き換えることによって取得されます。 置換プロセスは、型宣言のセマンティック意味に基づいており、単なるテキスト置換ではありません。
例: ジェネリック クラス宣言を指定する
class Gen<T,U> { public T[,] a; public void G(int i, T t, Gen<U,T> gt) {...} public U Prop { get {...} set {...} } public int H(double d) {...} }
構築された型
Gen<int[],IComparable<string>>
には、次のメンバーがあります。public int[,][] a; public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...} public IComparable<string> Prop { get {...} set {...} } public int H(double d) {...}
ジェネリック クラス宣言
Gen
でa
されるメンバーの型は "T
の 2 次元配列" であるため、上の構築された型のメンバーa
の型は"int
の 1 次元配列の 2 次元配列"、つまりint[,][]
。end の例
インスタンス関数メンバー内では、 this
の型は、包含宣言のインスタンス型 (§15.3.2) です。
ジェネリック クラスのすべてのメンバーは、外側の任意のクラスの型パラメーターを直接または構築された型の一部として使用できます。 実行時に特定の閉じた構築型 (§8.4.3) が使用される場合、型パラメーターの各使用は、構築された型に指定された型引数に置き換えられます。
例:
class C<V> { public V f1; public C<V> f2; public C(V x) { this.f1 = x; this.f2 = this; } } class Application { static void Main() { C<int> x1 = new C<int>(1); Console.WriteLine(x1.f1); // Prints 1 C<double> x2 = new C<double>(3.1415); Console.WriteLine(x2.f1); // Prints 3.1415 } }
end の例
15.3.4 継承
クラス inherits その直接基底クラスのメンバーです。 継承とは、基底クラスのインスタンス コンストラクター、ファイナライザー、静的コンストラクターを除き、クラスに直接基底クラスのすべてのメンバーが暗黙的に含まれていることを意味します。 継承の重要な側面は次のとおりです。
継承は推移的です。
C
がB
から派生し、B
がA
から派生している場合、C
は、B
で宣言されたメンバーと、A
で宣言されたメンバーを継承します。派生クラス extends その直接基底クラスです。 派生クラスは、継承するメンバーに新しいメンバーを追加できますが、継承されたメンバーの定義を削除することはできません。
インスタンス コンストラクター、ファイナライザー、および静的コンストラクターは継承されませんが、宣言されたアクセシビリティ (§7.5) に関係なく、他のすべてのメンバーは継承されます。 ただし、宣言されたアクセシビリティによっては、継承されたメンバーが派生クラスでアクセスできない場合があります。
派生クラスは、同じ名前またはシグネチャを持つ新しいメンバーを宣言することで (§7.7.2.3) 継承されたメンバーを表示できます。 ただし、継承されたメンバーを非表示にしても、そのメンバーは削除されず、派生クラスを介してそのメンバーに直接アクセスできなくなります。
クラスのインスタンスには、クラスとその基底クラスで宣言されているすべてのインスタンス フィールドのセットが含まれており、派生クラス型から基底クラス型への暗黙的な変換 (§10.2.8) が存在します。 したがって、派生クラスのインスタンスへの参照は、その基底クラスのインスタンスへの参照として扱うことができます。
クラスは仮想メソッド、プロパティ、インデクサー、およびイベントを宣言でき、派生クラスはこれらの関数メンバーの実装をオーバーライドできます。 これにより、クラスはポリモーフィックな動作を示すことができます。関数メンバーの呼び出しによって実行されるアクションは、その関数メンバーが呼び出されるインスタンスの実行時の型によって異なります。
構築されたクラス型の継承されたメンバーは、即時基底クラス型 (§15.2.4.2) のメンバーです。これは、 base_class_specificationで対応する型パラメーターが出現するたびに、構築された型の型引数を置き換えることによって検出されます。 これらのメンバーは、次に、置換することによって変換され、メンバー宣言内の各type_parameterについて、base_class_specificationの対応するtype_argument。
例:
class B<U> { public U F(long index) {...} } class D<T> : B<T[]> { public T G(string s) {...} }
上のコードでは、構築された型
D<int>
には、型パラメーターT
の型引数int
を置き換えることによって取得された、継承されていないメンバーパブリックint
G(string s)
があります。D<int>
には、クラス宣言B
から継承されたメンバーもあります。 この継承されたメンバーは、最初に基底クラス仕様のB<T[]>
でT
のint
を置き換えることによって、D<int>
の基底クラスの型B<int[]>
を決定することによって決定されます。 次に、B
の型引数として、int[]
がpublic U F(long index)
のU
に置き換わり、継承されたメンバーpublic int[] F(long index)
が生成されます。end の例
15.3.5 新しい修飾子
class_member_declarationは、継承されたメンバーと同じ名前またはシグネチャを持つメンバーを宣言できます。 これが発生すると、派生クラス メンバーは基底クラス メンバー表示されると言われます。 メンバーが継承されたメンバーを非表示にする正確な仕様については、 §7.7.2.3 を参照してください。
継承されたメンバー M
は、アクセス可能であると見なされますM
がアクセス可能であり、既にM
を非表示にする他の継承されたアクセス可能なメンバー N がない場合。 継承されたメンバーを暗黙的に非表示にしてもエラーとは見なされませんが、派生クラス メンバーの宣言に基本メンバーを非表示にすることを明示的に示す new
修飾子が含まれていない限り、コンパイラは警告を発行します。 入れ子になった型の部分宣言の 1 つ以上の部分 (§15.2.7) に new
修飾子が含まれている場合、入れ子になった型で使用可能な継承メンバーが非表示になっている場合、警告は発行されません。
使用可能な継承されたメンバーを非表示にしない宣言に new
修飾子が含まれている場合は、その効果に対する警告が発行されます。
15.3.6 アクセス修飾子
class_member_declarationは、許可されている種類の宣言されたアクセシビリティ (§7.5.2): public
、protected internal
、protected
、private protected
、internal
、またはprivate
のいずれかを持つことができます。 protected internal
とprivate protected
の組み合わせを除き、複数のアクセス修飾子を指定するのはコンパイル時エラーです。 class_member_declarationにアクセス修飾子が含まれていない場合は、private
が想定されます。
15.3.7 構成の種類
メンバーの宣言で使用される型は、そのメンバーの 構成型 呼び出されます。 構成可能な型は、定数、フィールド、プロパティ、イベント、またはインデクサーの型、メソッドまたは演算子の戻り値の型、およびメソッド、インデクサー、演算子、またはインスタンス コンストラクターのパラメーター型です。 メンバーの構成要素の種類は、少なくともそのメンバー自体と同じくらいアクセス可能である必要があります (§7.5.5)。
15.3.8 静的メンバーとインスタンス メンバー
クラスのメンバーは、 静的メンバー または instance メンバーです。
注: 一般に、静的メンバーはクラスに属し、インスタンス メンバーはオブジェクト (クラスのインスタンス) に属していると考えると便利です。 end note
フィールド、メソッド、プロパティ、イベント、演算子、またはコンストラクターの宣言に static
修飾子が含まれている場合は、静的メンバーを宣言します。 さらに、定数または型宣言は、静的メンバーを暗黙的に宣言します。 静的メンバーには、次の特性があります。
- フォーム
E.M
のmember_access (§12.8.7) で静的メンバーM
が参照されている場合、E
はメンバーM
を持つ型を表します。 インスタンスを示すE
のコンパイル時エラーです。 - 非ジェネリック クラスの静的フィールドは、1 つの格納場所を正確に識別します。 非ジェネリック クラスのインスタンスがいくつ作成されても、静的フィールドのコピーは 1 つだけです。 個別の閉じた構築型 (§8.4.3) には、閉じた構築型のインスタンスの数に関係なく、独自の静的フィールドのセットがあります。
- 静的関数メンバー (メソッド、プロパティ、イベント、演算子、またはコンストラクター) は特定のインスタンスで動作せず、このような関数メンバーでこれを参照するのはコンパイル時エラーです。
フィールド、メソッド、プロパティ、イベント、インデクサー、コンストラクター、またはファイナライザーの宣言に静的修飾子が含まれていない場合は、インスタンス メンバーを宣言します。 (インスタンス メンバーは、非静的メンバーと呼ばれることもあります)。インスタンス メンバーには、次の特性があります。
- フォーム
E.M
のmember_access (§12.8.7) でインスタンス メンバーM
が参照されている場合、E
はメンバーM
を持つ型のインスタンスを示す必要があります。 これは、型を示す E のバインド時エラーです。 - クラスのすべてのインスタンスには、クラスのすべてのインスタンス フィールドの個別のセットが含まれています。
- インスタンス関数メンバー (メソッド、プロパティ、インデクサー、インスタンス コンストラクター、またはファイナライザー) はクラスの特定のインスタンスで動作し、このインスタンスには
this
(§12.8.14) としてアクセスできます。
例: 次の例は、静的メンバーとインスタンス メンバーにアクセスするための規則を示しています。
class Test { int x; static int y; void F() { x = 1; // Ok, same as this.x = 1 y = 1; // Ok, same as Test.y = 1 } static void G() { x = 1; // Error, cannot access this.x y = 1; // Ok, same as Test.y = 1 } static void Main() { Test t = new Test(); t.x = 1; // Ok t.y = 1; // Error, cannot access static member through instance Test.x = 1; // Error, cannot access instance member through type Test.y = 1; // Ok } }
F
メソッドは、インスタンス関数メンバーで、simple_name (§12.8.4) を使用してインスタンス メンバーと静的メンバーの両方にアクセスできることを示しています。G
メソッドは、静的関数メンバーでは、simple_nameを介してインスタンス メンバーにアクセスするのはコンパイル時エラーであることを示しています。Main
メソッドは、member_access (§12.8.7) でインスタンス メンバーにインスタンスを介してアクセスし、静的メンバーが型を介してアクセスされることを示しています。end の例
15.3.9 入れ子になった型
15.3.9.1 全般
クラスまたは構造体内で宣言された型は、 nested 型と呼ばれます。 コンパイル単位または名前空間内で宣言された型は、 非入れ子の型と呼ばれます。
例: 次の例では、
class A { class B { static void F() { Console.WriteLine("A.B.F"); } } }
クラス
B
は、クラスA
内で宣言されているため入れ子になった型であり、クラスA
はコンパイル 単位内で宣言されているため、入れ子になっていない型です。end の例
15.3.9.2 完全修飾名
入れ子になった型宣言の完全修飾名 (§7.8.3) S.N
S
は、N
が宣言され、される型宣言の完全修飾名です。 N
は、入れ子になった型宣言の非修飾名 (§7.8.2) です (任意のgeneric_dimension_specifier (§12.8.18 を含む)。
15.3.9.3 宣言されたアクセシビリティ
入れ子になっていない型は、 public
または internal
宣言されたアクセシビリティを持ち、既定で internal
宣言されたアクセシビリティを持つことができます。 入れ子になった型は、これらの形式の宣言されたアクセシビリティに加えて、含まれる型がクラスか構造体かに応じて、1 つ以上の追加の形式の宣言されたアクセシビリティを持つことができます。
- クラスで宣言されている入れ子になった型は、許可されている任意の種類の宣言されたアクセシビリティを持つ場合があり、他のクラス メンバーと同様に、既定では宣言されたアクセシビリティ
private
。 - 構造体で宣言されている入れ子になった型には、宣言されたアクセシビリティ (
public
、internal
、またはprivate
) の 3 つの形式があり、他の構造体メンバーと同様に、既定で宣言されたアクセシビリティprivate
。
例: 例
public class List { // Private data structure private class Node { public object Data; public Node? Next; public Node(object data, Node? next) { this.Data = data; this.Next = next; } } private Node? first = null; private Node? last = null; // Public interface public void AddToFront(object o) {...} public void AddToBack(object o) {...} public object RemoveFromFront() {...} public object RemoveFromBack() {...} public int Count { get {...} } }
は、入れ子になったプライベート クラス
Node
を宣言します。end の例
15.3.9.4 非表示
入れ子になった型は、基本メンバー (§7.7.2.2) を非表示にすることができます。 new
修飾子 (§15.3.5) は、入れ子になった型宣言で許可されるため、非表示を明示的に表すことができます。
例: 例
class Base { public static void M() { Console.WriteLine("Base.M"); } } class Derived: Base { public new class M { public static void F() { Console.WriteLine("Derived.M.F"); } } } class Test { static void Main() { Derived.M.F(); } }
は、
Base
で定義されているメソッドM
を非表示にする入れ子になったクラスM
を示しています。end の例
15.3.9.5 このアクセス
入れ子になった型とその包含型は、 this_access (§12.8.14) に関して特別な関係を持っていません。 具体的には、入れ子になった型内の this
を使用して、包含型のインスタンス メンバーを参照することはできません。 入れ子になった型が、その包含型のインスタンス メンバーにアクセスする必要がある場合は、入れ子になった型のコンストラクター引数として、包含型のインスタンスの this
を指定することで、アクセスを提供できます。
例: 次の例
class C { int i = 123; public void F() { Nested n = new Nested(this); n.G(); } public class Nested { C this_c; public Nested(C c) { this_c = c; } public void G() { Console.WriteLine(this_c.i); } } } class Test { static void Main() { C c = new C(); c.F(); } }
は、この手法を示しています。
C
のインスタンスは、Nested
のインスタンスを作成し、それ自体をNested
のコンストラクターに渡して、C
のインスタンス メンバーへの後続のアクセスを提供します。end の例
15.3.9.6 包含型のプライベートおよび保護されたメンバーへのアクセス
入れ子になった型は、 private
と宣言されたアクセシビリティを持つ包含型のメンバーを含め、その包含型からアクセスできるすべてのメンバー protected
アクセスできます。
例: 例
class C { private static void F() => Console.WriteLine("C.F"); public class Nested { public static void G() => F(); } } class Test { static void Main() => C.Nested.G(); }
には、入れ子になったクラス
Nested
を含むクラスC
が表示されます。Nested
内では、メソッドG
は、C
で定義F
静的メソッドを呼び出し、F
にはプライベートで宣言されたアクセシビリティがあります。end の例
入れ子になった型は、その包含型の基本型で定義されている保護されたメンバーにもアクセスできます。
例: 次のコード内
class Base { protected void F() => Console.WriteLine("Base.F"); } class Derived: Base { public class Nested { public void G() { Derived d = new Derived(); d.F(); // ok } } } class Test { static void Main() { Derived.Nested n = new Derived.Nested(); n.G(); } }
入れ子になったクラス
Derived.Nested
は、Derived
のインスタンスを介して呼び出すことによって、Derived
の基底クラス (Base
) で定義されている保護されたメソッドF
にアクセスします。end の例
15.3.9.7 ジェネリック クラスの入れ子になった型
ジェネリック クラス宣言には、入れ子になった型宣言を含める場合があります。 外側のクラスの型パラメーターは、入れ子になった型内で使用できます。 入れ子になった型宣言には、入れ子になった型にのみ適用される追加の型パラメーターを含める場合があります。
ジェネリック クラス宣言に含まれるすべての型宣言は、暗黙的にジェネリック型宣言です。 ジェネリック型内で入れ子になった型への参照を書き込む場合は、その型引数を含む、含む構築された型に名前を付ける必要があります。 ただし、外側のクラス内から、入れ子になった型は修飾なしで使用できます。外側のクラスのインスタンス型は、入れ子になった型を構築するときに暗黙的に使用できます。
例: 次に示すのは、
Inner
から作成された構築型を参照する 3 つの正しい方法です。最初の 2 つは同等です。class Outer<T> { class Inner<U> { public static void F(T t, U u) {...} } static void F(T t) { Outer<T>.Inner<string>.F(t, "abc"); // These two statements have Inner<string>.F(t, "abc"); // the same effect Outer<int>.Inner<string>.F(3, "abc"); // This type is different Outer.Inner<string>.F(t, "abc"); // Error, Outer needs type arg } }
end の例
プログラミング スタイルは不適切ですが、入れ子になった型の型パラメーターでは、外側の型で宣言されたメンバーまたは型パラメーターが非表示になる可能性があります。
例:
class Outer<T> { class Inner<T> // Valid, hides Outer's T { public T t; // Refers to Inner's T } }
end の例
15.3.10 予約済みメンバー名
15.3.10.1 全般
基になる C# ランタイムの実装を容易にするために、 プロパティ、イベント、またはインデクサーである各ソース メンバー宣言について、実装では、メンバー宣言の種類、名前、およびその型 (§15.3.10.2、 §15.3.10.3、 §15.3.10.4) に基づいて 2 つのメソッド シグネチャを予約する必要があります。 基になるランタイム実装でこれらの予約が使用されていない場合でも、同じスコープで宣言されたメンバーによって予約された署名と一致する署名を宣言するのは、プログラムのコンパイル時エラーです。
予約名は宣言を導入しないため、メンバー参照には参加しません。 ただし、宣言に関連付けられている予約メソッドシグネチャは継承 (§15.3.4) に参加し、 new
修飾子 (§15.3.5) を使用して非表示にすることができます。
注: これらの名前の予約は、次の 3 つの目的に対応します。
- 基になる実装で、C# 言語機能へのアクセスを取得または設定するためのメソッド名として通常の識別子を使用できるようにします。
- C# 言語機能へのアクセスを取得または設定するためのメソッド名として通常の識別子を使用して、他の言語が相互運用できるようにするため。
- すべての C# 実装で予約済みメンバー名の詳細を一貫性を持たれることで、準拠しているコンパイラによって受け入れられるソースが別のコンパイラによって受け入れられるようにするため。
end note
ファイナライザー (§15.13) の宣言により、署名も予約されます (§15.3.10.5)。
一部の名前は、演算子メソッド名として使用するために予約されています (§15.3.10.6)。
15.3.10.2 プロパティ用に予約されたメンバー名
T
型のプロパティ P
(§15.7) の場合、次の署名が予約されています。
T get_P();
void set_P(T value);
プロパティが読み取り専用または書き込み専用の場合でも、両方のシグネチャが予約されています。
例: 次のコード内
class A { public int P { get => 123; } } class B : A { public new int get_P() => 456; public new void set_P(int value) { } } class Test { static void Main() { B b = new B(); A a = b; Console.WriteLine(a.P); Console.WriteLine(b.P); Console.WriteLine(b.get_P()); } }
クラス
A
は、読み取り専用プロパティP
を定義するため、get_P
メソッドとset_P
メソッドのシグネチャを予約します。A
クラスB
A
から派生し、これらの予約済み署名の両方を非表示にします。 この例では、次の出力が生成されます。123 123 456
end の例
15.3.10.3 イベント用に予約されたメンバー名
デリゲート型T
のイベント E
(§15.8) の場合、次の署名が予約されます。
void add_E(T handler);
void remove_E(T handler);
15.3.10.4 インデクサー用に予約されたメンバー名
パラメーター リスト L
を持つT
型のインデクサー (§15.9) の場合、次のシグネチャが予約されます。
T get_Item(L);
void set_Item(L, T value);
インデクサーが読み取り専用または書き込み専用の場合でも、両方の署名が予約されています。
さらに、メンバー名 Item
は予約されています。
15.3.10.5 ファイナライザー用に予約されたメンバー名
ファイナライザー (§15.13 を含むクラスの場合、次のシグネチャが予約されています。
void Finalize();
15.3.10.6 演算子用に予約されたメソッド名
次のメソッド名は予約されています。 多くの場合、この仕様には対応する演算子が含まれていますが、一部は将来のバージョンで使用するために予約されていますが、一部は他の言語との相互運用のために予約されています。
メソッド名 | C# 演算子 |
---|---|
op_Addition |
+ (バイナリ) |
op_AdditionAssignment |
(予約済み) |
op_AddressOf |
(予約済み) |
op_Assign |
(予約済み) |
op_BitwiseAnd |
& (バイナリ) |
op_BitwiseAndAssignment |
(予約済み) |
op_BitwiseOr |
\| |
op_BitwiseOrAssignment |
(予約済み) |
op_CheckedAddition |
(将来の使用のために予約済み) |
op_CheckedDecrement |
(将来の使用のために予約済み) |
op_CheckedDivision |
(将来の使用のために予約済み) |
op_CheckedExplicit |
(将来の使用のために予約済み) |
op_CheckedIncrement |
(将来の使用のために予約済み) |
op_CheckedMultiply |
(将来の使用のために予約済み) |
op_CheckedSubtraction |
(将来の使用のために予約済み) |
op_CheckedUnaryNegation |
(将来の使用のために予約済み) |
op_Comma |
(予約済み) |
op_Decrement |
-- (プレフィックスと後置) |
op_Division |
/ |
op_DivisionAssignment |
(予約済み) |
op_Equality |
== |
op_ExclusiveOr |
^ |
op_ExclusiveOrAssignment |
(予約済み) |
op_Explicit |
明示的 (縮小) 強制型化 |
op_False |
false |
op_GreaterThan |
> |
op_GreaterThanOrEqual |
>= |
op_Implicit |
暗黙的 (拡大) 強制型変換 |
op_Increment |
++ (プレフィックスと後置) |
op_Inequality |
!= |
op_LeftShift |
<< |
op_LeftShiftAssignment |
(予約済み) |
op_LessThan |
< |
op_LessThanOrEqual |
<= |
op_LogicalAnd |
(予約済み) |
op_LogicalNot |
! |
op_LogicalOr |
(予約済み) |
op_MemberSelection |
(予約済み) |
op_Modulus |
% |
op_ModulusAssignment |
(予約済み) |
op_MultiplicationAssignment |
(予約済み) |
op_Multiply |
* (バイナリ) |
op_OnesComplement |
~ |
op_PointerDereference |
(予約済み) |
op_PointerToMemberSelection |
(予約済み) |
op_RightShift |
>> |
op_RightShiftAssignment |
(予約済み) |
op_SignedRightShift |
(予約済み) |
op_Subtraction |
- (バイナリ) |
op_SubtractionAssignment |
(予約済み) |
op_True |
true |
op_UnaryNegation |
- (単項) |
op_UnaryPlus |
+ (単項) |
op_UnsignedRightShift |
(将来の使用のために予約済み) |
op_UnsignedRightShiftAssignment |
(予約済み) |
15.4 定数
定数は、定数値 (コンパイル時に計算できる値) を表すクラス メンバーです。 constant_declarationでは、特定の型の 1 つ以上の定数が導入されます。
constant_declaration
: attributes? constant_modifier* 'const' type constant_declarators ';'
;
constant_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
;
constant_declarationには、一連のattributes (§22)、new
修飾子 (§15.3.5)、および許可されている種類の宣言されたアクセシビリティ (§15.3.6) を含めることができます。 属性と修飾子は、 constant_declarationによって宣言されたすべてのメンバーに適用されます。 定数は静的メンバーと見なされますが、 constant_declaration は static
修飾子を必要とせず、許可もしません。 定数宣言で同じ修飾子が複数回出現するのはエラーです。
constant_declarationのtypeは、宣言によって導入されたメンバーの型を指定します。 型の後に constant_declaratorの一覧 (§13.6.3) が続き、それぞれに新しいメンバーが導入されます。 constant_declaratorは、メンバーに名前を付ける identifier、その後に "=
" トークン、その後にメンバーの値を指定するconstant_expression (§12.23) で構成されます。
定数宣言で指定する 型 は、 sbyte
、 byte
、 short
、 ushort
、 int
、 uint
、 long
、 ulong
、 char
、 float
、 double
、 decimal
、 bool
、 string
、 enum_type、または reference_typeである必要があります。 各 constant_expression は、暗黙的な変換 (§10.2) によってターゲット型に変換できるターゲット型または型の値を生成します。
定数の 型 は、定数自体と同じくらいアクセス可能である必要があります (§7.5.5)。
定数の値は、 simple_name (§12.8.4) または member_access (§12.8.7) を使用して式で取得されます。
定数自体は、 constant_expressionに参加できます。 したがって、定数は、 constant_expressionを必要とする任意のコンストラクトで使用できます。
注: このようなコンストラクトの例には、
case
ラベル、goto case
ステートメント、enum
メンバー宣言、属性、およびその他の定数宣言が含まれます。 end note
注: §12.23 で説明されているように、 constant_expression はコンパイル時に完全に評価できる式です。
string
以外のreference_typeの null 以外の値を作成する唯一の方法はnew
演算子を適用することであり、new
演算子はconstant_expressionでは許可されないため、string
以外のreference_typeの定数に使用できる値はnull
だけです。 end note
定数値のシンボリック名が必要な場合、その値の型が定数宣言で許可されていない場合、または constant_expressionによってコンパイル時に値を計算できない場合は、代わりに読み取り専用フィールド (§15.5.3) を使用できます。
注:
const
とreadonly
のバージョン管理セマンティクスは異なります (§15.5.3.3)。 end note
複数の定数を宣言する定数宣言は、同じ属性、修飾子、および型を持つ 1 つの定数の複数の宣言と同じです。
例:
class A { public const double X = 1.0, Y = 2.0, Z = 3.0; }
上記の式は、次の式と同じです。
class A { public const double X = 1.0; public const double Y = 2.0; public const double Z = 3.0; }
end の例
依存関係が循環的でない限り、定数は同じプログラム内の他の定数に依存できます。 コンパイラは、定数宣言を適切な順序で評価するように自動的に配置します。
例: 次のコード内
class A { public const int X = B.Z + 1; public const int Y = 10; } class B { public const int Z = A.Y + 1; }
コンパイラは最初に
A.Y
を評価し、次にB.Z
を評価し、最後にA.X
を評価して、10
、11
、および12
値を生成します。end の例
定数宣言は他のプログラムの定数に依存する場合がありますが、このような依存関係は一方向でのみ可能です。
例: 上記の例を参照すると、
A
とB
が別々のプログラムで宣言されている場合、A.X
はB.Z
に依存できますが、B.Z
は同時にA.Y
に依存できません。 end の例
15.5 フィールド
15.5.1 全般
フィールドは、オブジェクトまたはクラスに関連付けられた変数を表すメンバーです。 field_declarationでは、特定の型の 1 つ以上のフィールドが導入されます。
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| unsafe_modifier // unsafe code support
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
field_declarationには、一連の attributes (§22)、new
修飾子 (§15.3.5 を含めることができます。 4 つのアクセス修飾子 (§15.3.6)、およびstatic
修飾子 (§15.5.2) の有効な組み合わせ。 さらに、 field_declaration には、 readonly
修飾子 (§15.5.3) または volatile
修飾子 (§15.5.4) を含めることができますが、両方を含むわけではありません。 属性と修飾子は、 field_declarationによって宣言されたすべてのメンバーに適用されます。 field_declarationで同じ修飾子が複数回表示されるというエラーです。
field_declarationのtypeは、宣言によって導入されたメンバーの型を指定します。 型の後に variable_declaratorの一覧が続き、それぞれに新しいメンバーが導入されます。 variable_declaratorは、そのメンバーに名前を付けるidentifier、必要に応じて "=
" トークンと、そのメンバーの初期値を提供するvariable_initializer (§15.5.6) で構成されます。
フィールドの type は、少なくともフィールド自体と同じくらいアクセス可能である必要があります (§7.5.5)。
フィールドの値は、 simple_name (§12.8.4)、 member_access (§12.8.7) またはbase_access (§12.8.15) を使用して式で取得されます。 読み取り専用でないフィールドの値は、 assignment (§12.21) を使用して変更されます。 読み取り専用でないフィールドの値は、後置インクリメント演算子とデクリメント演算子 (§12.8.16) とプレフィックスインクリメント演算子とデクリメント演算子 (§12.9.6) を使用して取得および変更できます。
複数のフィールドを宣言するフィールド宣言は、同じ属性、修飾子、および型を持つ 1 つのフィールドの複数の宣言と同じです。
例:
class A { public static int X = 1, Y, Z = 100; }
上記の式は、次の式と同じです。
class A { public static int X = 1; public static int Y; public static int Z = 100; }
end の例
15.5.2 静的フィールドとインスタンス フィールド
フィールド宣言に static
修飾子が含まれている場合、宣言によって導入されたフィールドは 静的フィールド。 static
修飾子が存在しない場合、宣言によって導入されたフィールドはinstance フィールド。 静的フィールドとインスタンス フィールドは、C# でサポートされる複数の種類の変数 (§9) の 2 つであり、それぞれ静的変数インスタンス変数と呼ばれる場合があります。
§15.3.8 で説明したようにクラスの各インスタンスには、クラスのインスタンス フィールドの完全なセットが含まれますが、クラスのインスタンス数または閉じた構築型の数に関係なく、非ジェネリック クラスまたは閉じた構築された型ごとに静的フィールドのセットは 1 つだけあります。
15.5.3 読み取りフィールド
15.5.3.1 全般
field_declarationにreadonly
修飾子が含まれている場合、宣言によって導入されるフィールドは readonly フィールド。 読み取り専用フィールドへの直接割り当ては、その宣言の一部として、または同じクラスのインスタンス コンストラクターまたは静的コンストラクター内でのみ行うことができます。 (これらのコンテキストでは、読み取り専用フィールドを複数回割り当てることができます)。具体的には、読み取り専用フィールドへの直接割り当ては、次のコンテキストでのみ許可されます。
- フィールドを導入する variable_declarator (宣言に variable_initializer を含めます)。
- インスタンス フィールドの場合、フィールド宣言を含むクラスのインスタンス コンストラクター内。静的フィールドの場合は、フィールド宣言を含むクラスの静的コンストラクター内。 これらは、読み取り専用フィールドを出力または参照パラメーターとして渡すことが有効な唯一のコンテキストでもあります。
読み取り専用フィールドに割り当てたり、他のコンテキストで出力または参照パラメーターとして渡そうとすると、コンパイル時エラーになります。
15.5.3.2 定数に静的な読み取りフィールドを使用する
静的読み取り専用フィールドは、定数値のシンボリック名が必要な場合、または定数宣言で値の型が許可されていない場合、またはコンパイル時に値を計算できない場合に便利です。
例: 次のコード内
public class Color { public static readonly Color Black = new Color(0, 0, 0); public static readonly Color White = new Color(255, 255, 255); public static readonly Color Red = new Color(255, 0, 0); public static readonly Color Green = new Color(0, 255, 0); public static readonly Color Blue = new Color(0, 0, 255); private byte red, green, blue; public Color(byte r, byte g, byte b) { red = r; green = g; blue = b; } }
コンパイル時に値を計算できないため、
Black
、White
、Red
、Green
、およびBlue
メンバーを const メンバーとして宣言することはできません。 ただし、それらをstatic readonly
宣言すると、同じ効果が得られます。end の例
15.5.3.3 定数と静的読み取りonly フィールドのバージョン管理
定数と読み取りonly フィールドには、異なるバイナリ バージョン管理セマンティクスがあります。 式が定数を参照する場合、定数の値はコンパイル時に取得されますが、式が読み取り専用フィールドを参照する場合、フィールドの値は実行時まで取得されません。
例: 2 つの別個のプログラムで構成されるアプリケーションを考えてみましょう。
namespace Program1 { public class Utils { public static readonly int x = 1; } }
and
namespace Program2 { class Test { static void Main() { Console.WriteLine(Program1.Utils.X); } } }
Program1
名前空間とProgram2
名前空間は、個別にコンパイルされる 2 つのプログラムを表します。Program1.Utils.X
はstatic readonly
フィールドとして宣言されているため、Console.WriteLine
ステートメントによって出力される値はコンパイル時には認識されず、実行時に取得されます。 したがって、X
の値が変更され、Program1
が再コンパイルされた場合、Program2
再コンパイルされていない場合でも、Console.WriteLine
ステートメントによって新しい値が出力されます。 ただし、X
定数であった場合、X
の値はProgram2
コンパイル時に取得され、Program2
が再コンパイルされるまでProgram1
の変更の影響を受けることはありません。end の例
15.5.4 揮発性フィールド
field_declarationにvolatile
修飾子が含まれている場合、その宣言によって導入されるフィールドは揮発性フィールドです。 不揮発性フィールドの場合、命令を並べ替える最適化手法は、 lock_statement (§13.13) によって提供される同期なしでフィールドにアクセスするマルチスレッド プログラムで予期しない予期しない結果を招く可能性があります。 これらの最適化は、コンパイラ、ランタイム システム、またはハードウェアによって実行できます。 揮発性フィールドの場合、このような並べ替えの最適化は制限されます。
- 揮発性フィールドの読み取りは、 揮発性読み取りと呼ばれます。 揮発性の読み取りには"取得セマンティクス"があります。つまり、命令シーケンスの後に発生するメモリへの参照の前に発生することが保証されます。
- 揮発性フィールドの書き込みは、 揮発性書き込みと呼ばれます。 揮発性書き込みには "リリース セマンティクス" があります。つまり、命令シーケンスの書き込み命令の前にメモリ参照が行われると保証されます。
これらの制限により、すべてのスレッドが、他のスレッドによって実行された volatile の書き込みを、実行された順序で観察することが保証されます。 準拠した実装では、すべての実行スレッドから見た揮発性の書き込みの 1 つの合計順序を指定する必要はありません。 揮発性フィールドの型は、次のいずれかになります。
- reference_type。
- 参照型であることが知られている type_parameter (§15.2.5)。
- 型
byte
、sbyte
、short
、ushort
、int
、uint
、char
、float
、bool
、System.IntPtr
、またはSystem.UIntPtr
。 byte
、sbyte
、short
、ushort
、int
、またはuint
のenum_base型を持つenum_type。
例: 例
class Test { public static int result; public static volatile bool finished; static void Thread2() { result = 143; finished = true; } static void Main() { finished = false; // Run Thread2() in a new thread new Thread(new ThreadStart(Thread2)).Start(); // Wait for Thread2() to signal that it has a result // by setting finished to true. for (;;) { if (finished) { Console.WriteLine($"result = {result}"); return; } } } }
この例では、次のように出力されます。
result = 143
この例では、メソッド
Main
は、メソッドThread2
を実行する新しいスレッドを開始します。 このメソッドは、result
と呼ばれる非揮発性フィールドに値を格納し、揮発性フィールドfinished
にtrue
を格納します。 メイン スレッドは、フィールドfinished
がtrue
に設定されるのを待ってから、フィールドresult
を読み取ります。finished
はvolatile
宣言されているため、メイン スレッドはフィールドresult
から143
値を読み取ります。 フィールドfinished
volatile
宣言されていない場合は、ストアがメイン スレッド後ストアをfinished
して、メイン スレッドがフィールドresult
から値 0 を読み取ることをresult
できます。finished
をvolatile
フィールドとして宣言すると、このような不整合が回避されます。end の例
15.5.5 フィールドの初期化
フィールドの初期値は、静的フィールドでもインスタンス フィールドでも、フィールドの型の既定値 (§9.3) です。 この既定の初期化が行われる前にフィールドの値を確認することはできず、フィールドが "初期化されていません" になることはありません。
例: 例
class Test { static bool b; int i; static void Main() { Test t = new Test(); Console.WriteLine($"b = {b}, i = {t.i}"); } }
この例では、次のように出力されます。
b = False, i = 0
b
とi
の両方が自動的に既定値に初期化されるためです。end の例
15.5.6 変数初期化子
15.5.6.1 全般
フィールド宣言には、 variable_initializerを含めることができます。 静的フィールドの場合、変数初期化子は、クラスの初期化中に実行される代入ステートメントに対応します。 インスタンス フィールドの場合、変数初期化子は、クラスのインスタンスの作成時に実行される代入ステートメントに対応します。
例: 例
class Test { static double x = Math.Sqrt(2.0); int i = 100; string s = "Hello"; static void Main() { Test a = new Test(); Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}"); } }
この例では、次のように出力されます。
x = 1.4142135623730951, i = 100, s = Hello
静的フィールド初期化子の実行時に
x
への割り当てが発生し、インスタンス フィールド初期化子の実行時にi
とs
への割り当てが発生するためです。end の例
§15.5.5 で説明されている既定値の初期化は、変数初期化子を持つフィールドを含むすべてのフィールドに対して行われます。 したがって、クラスが初期化されると、そのクラス内のすべての静的フィールドが最初に既定値に初期化され、次に静的フィールド初期化子がテキスト順で実行されます。 同様に、クラスのインスタンスが作成されると、そのインスタンス内のすべてのインスタンス フィールドが最初に既定値に初期化され、次にインスタンス フィールド初期化子がテキスト順で実行されます。 同じ型の複数の部分型宣言にフィールド宣言がある場合、部分の順序は指定されません。 ただし、各パーツ内では、フィールド初期化子が順番に実行されます。
変数初期化子を持つ静的フィールドは、既定値の状態で観察される可能性があります。
例: ただし、これはスタイルの問題として強く推奨されません。 例を示します。
class Test { static int a = b + 1; static int b = a + 1; static void Main() { Console.WriteLine($"a = {a}, b = {b}"); } }
は、この動作を示します。
a
とb
の循環定義にもかかわらず、プログラムは有効です。 結果として出力されます。a = 1, b = 2
静的フィールド
a
とb
は初期化子が実行される前に0
(int
の既定値) に初期化されるためです。a
の初期化子を実行すると、b
の値は 0 であるため、a
は1
に初期化されます。b
の初期化子を実行すると、a の値は既に1
されているため、b
は2
に初期化されます。end の例
15.5.6.2 静的フィールドの初期化
クラスの静的フィールド変数初期化子は、クラス宣言 (§15.5.6.1) に表示されるテキストの順序で実行される割り当てのシーケンスに対応します。 部分クラス内では、"textual order" の意味は §15.5.6.1 で指定されます。 クラスに静的コンストラクター (§15.12) が存在する場合、静的フィールド初期化子の実行は、その静的コンストラクターを実行する直前に実行されます。 それ以外の場合、静的フィールド初期化子は、そのクラスの静的フィールドを最初に使用する前に、実装に依存する時間に実行されます。
例: 例
class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { public static int X = Test.F("Init A"); } class B { public static int Y = Test.F("Init B"); }
は、次のいずれかの出力を生成する場合があります。
Init A Init B 1 1
または出力:
Init B Init A 1 1
X
の初期化子とY
の初期化子の実行はどちらの順序でも発生する可能性があるため、これらのフィールドへの参照の前にのみ制約されます。 ただし、この例では次のようになります。class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { static A() {} public static int X = Test.F("Init A"); } class B { static B() {} public static int Y = Test.F("Init B"); }
出力は次のようになります。
Init B Init A 1 1
静的コンストラクターを実行する場合の規則 ( §15.12 で定義されている) は、
B
の静的コンストラクター (およびB
の静的フィールド初期化子) が、A
の静的コンストラクターとフィールド初期化子の前に実行されることを提供するためです。end の例
15.5.6.3 インスタンス フィールドの初期化
クラスのインスタンス フィールド変数初期化子は、そのクラスのいずれかのインスタンス コンストラクター (§15.11.3) へのエントリの直後に実行される割り当てのシーケンスに対応します。 部分クラス内では、"textual order" の意味は §15.5.6.1 で指定されます。 変数初期化子は、クラス宣言 (§15.5.6.1) に表示されるテキストの順序で実行されます。 クラス インスタンスの作成と初期化プロセスについては、 §15.11 で詳しく説明します。
インスタンス フィールドの変数初期化子は、作成されるインスタンスを参照できません。 したがって、変数初期化子で this
を参照するのはコンパイル時エラーです。これは、変数初期化子が simple_nameを介してインスタンス メンバーを参照するためのコンパイル時エラーであるためです。
例: 次のコード内
class A { int x = 1; int y = x + 1; // Error, reference to instance member of this }
y
の変数初期化子は、作成中のインスタンスのメンバーを参照するため、コンパイル時エラーが発生します。end の例
15.6 メソッド
15.6.1 全般
"メソッド" は、オブジェクトまたはクラスによって実行可能な計算またはアクションを実装するメンバーです。 メソッドは、 method_declarationを使用して宣言されます。
method_declaration
: attributes? method_modifiers return_type method_header method_body
| attributes? ref_method_modifiers ref_kind ref_return_type method_header
ref_method_body
;
method_modifiers
: method_modifier* 'partial'?
;
ref_kind
: 'ref'
| 'ref' 'readonly'
;
ref_method_modifiers
: ref_method_modifier*
;
method_header
: member_name '(' parameter_list? ')'
| member_name type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
method_modifier
: ref_method_modifier
| 'async'
;
ref_method_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
return_type
: ref_return_type
| 'void'
;
ref_return_type
: type
;
member_name
: identifier
| interface_type '.' identifier
;
method_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
| ';'
;
ref_method_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
文法に関する注意事項:
- unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
- null_conditional_invocation_expressionと式の両方が適用される場合、method_bodyを認識する場合は前者を選択する必要があります。
注: ここでの代替手段の重複と優先順位は、説明上の便宜上のみであり、文法規則を詳しく説明して重複を取り除く場合があります。 ANTLR やその他の文法システムでは、同じ利便性が採用されているため、 method_body には指定されたセマンティクスが自動的に適用されます。 end note
method_declarationには、一連のattributes (§22) と、許可されている種類の宣言されたアクセシビリティ (§15.3.6) が含まれる場合があります。 new
(§15.3.5)、static
(§15.6.3)、virtual
(§15.6.. 4)、override
(§15.6.5)、sealed
(§15.6.6)、abstract
(§2) 15.6.7)、extern
(§15.6.8) および async
(§15.15) 修飾子。
次のすべてが当てはまる場合、宣言には修飾子の有効な組み合わせがあります。
- 宣言には、アクセス修飾子の有効な組み合わせが含まれています (§15.3.6)。
- 宣言に同じ修飾子が複数回含まれていない。
- 宣言には、
static
、virtual
、override
のいずれかの修飾子が含まれます。 - 宣言には、
new
とoverride
のいずれかの修飾子が含まれます。 - 宣言に
abstract
修飾子が含まれている場合、宣言には、static
、virtual
、sealed
、またはextern
のいずれかの修飾子は含まれません。 - 宣言に
private
修飾子が含まれている場合、宣言には、virtual
、override
、またはabstract
のいずれかの修飾子は含まれません。 - 宣言に
sealed
修飾子が含まれている場合、宣言にはoverride
修飾子も含まれます。 - 宣言に
partial
修飾子が含まれている場合、new
、public
、protected
、internal
、private
、virtual
、sealed
、override
、abstract
、またはextern
のいずれかの修飾子は含まれません。
メソッドは、何が返されるかに応じて分類されます。
ref
が存在する場合、メソッドは returns-by-ref で、必要に応じて読み取り専用である可変参照を返します。- それ以外の場合、 return_type が
void
の場合、メソッドは returns-no-value 値を返しません。 - それ以外の場合、メソッドは値 返され 値が返されます。
returns-by-value メソッド宣言または returns-no-value メソッド宣言の return_type は、メソッドによって返される結果の型 (存在する場合) を指定します。 returns-no-value メソッドにのみ、 partial
修飾子 (§15.6.9) を含めることができます。 宣言に async
修飾子が含まれている場合、 return_type は void
されるか、メソッドが値によって返され、戻り値の型は task 型 (§15.15.1) になります。
returns by-ref メソッド宣言の ref_return_type は、メソッドによって返される variable_reference によって参照される変数の型を指定します。
ジェネリック メソッドは、宣言に type_parameter_listが含まれるメソッドです。 これにより、メソッドの型パラメーターが指定されます。 省略可能な type_parameter_constraints_clauseでは、型パラメーターの制約を指定します。
明示的なインターフェイス メンバー実装のジェネリック method_declaration には、 type_parameter_constraints_clauseは含まれません。宣言は、インターフェイス メソッドの制約から制約を継承します。
同様に、 override
修飾子を持つメソッド宣言には type_parameter_constraints_clauseを含めず、メソッドの型パラメーターの制約はオーバーライドされる仮想メソッドから継承されます。
member_nameは、メソッドの名前を指定します。 メソッドが明示的なインターフェイス メンバー実装 (§18.6.2) でない限り、 member_name は単に identifier です。
明示的なインターフェイス メンバー実装の場合、 member_name は interface_type の後に ".
" と identifierで構成されます。 この場合、宣言には、(場合によっては) extern
または async
以外の修飾子を含めてはならない。
省略可能な parameter_list は、メソッドのパラメーターを指定します (§15.6.2)。
return_typeまたはref_return_type、およびメソッドのparameter_listで参照される各型は、少なくともメソッド自体と同じくらいアクセス可能である必要があります (§7.5.5)。
returns-by-value メソッドまたは returns-no-value メソッドの method_body は、セミコロン、ブロック本体、または式本体のいずれかです。 ブロック本体は、メソッドの呼び出し時に実行するステートメントを指定する ブロックで構成されます。 式本体は、 =>
、 null_conditional_invocation_expression または 式セミコロンで構成され、メソッドの呼び出し時に実行する単一の式を表します。
抽象メソッドと extern メソッドの場合、 method_body は単にセミコロンで構成されます。 部分メソッドの場合、 method_body はセミコロン、ブロック本体、または式本体で構成できます。 他のすべてのメソッドでは、 method_body はブロック本体または式本体です。
method_bodyがセミコロンで構成されている場合、宣言にはasync
修飾子は含まれません。
returns by-ref メソッドの ref_method_body は、セミコロン、ブロック本文、または式本体のいずれかです。 ブロック本体は、メソッドの呼び出し時に実行するステートメントを指定する ブロックで構成されます。 式本体は、 =>
、 ref
、 variable_reference、セミコロンで構成され、メソッドの呼び出し時に評価する 1 つの variable_reference を表します。
抽象メソッドと extern メソッドの場合、 ref_method_body は単にセミコロンで構成されます。他のすべてのメソッドでは、 ref_method_body はブロック本体または式本体のいずれかです。
名前、型パラメーターの数、およびメソッドのパラメーター リストは、メソッドのシグネチャ (§7.6) を定義します。 具体的には、メソッドのシグネチャは、名前、型パラメーターの数、および数、 parameter_mode_modifier (§15.6.2.1)、およびパラメーターの型で構成されます。 戻り値の型は、メソッドのシグネチャの一部ではなく、パラメーターの名前、型パラメーターの名前、または制約でもありません。 パラメーター型がメソッドの型パラメーターを参照する場合、型パラメーターの序数位置 (型パラメーターの名前ではなく) が型の等価性に使用されます。
メソッドの名前は、同じクラスで宣言されている他のすべての非メソッドの名前とは異なります。 さらに、メソッドのシグネチャは、同じクラスで宣言されている他のすべてのメソッドのシグネチャと異なる必要があります。また、同じクラスで宣言された 2 つのメソッドには、 in
、 out
、および ref
によってのみ異なるシグネチャは含まれません。
メソッドの type_parameterは、 method_declaration全体でスコープ内にあり、 return_type または ref_return_type、 method_body または ref_method_body、および type_parameter_constraints_clauseで、 attributes ではなく、そのスコープ全体で型を形成するために使用できます。
すべてのパラメーターと型パラメーターの名前は異なります。
15.6.2 メソッドパラメーター
15.6.2.1 全般
メソッドのパラメーター (存在する場合) は、メソッドの parameter_listによって宣言されます。
parameter_list
: fixed_parameters
| fixed_parameters ',' parameter_array
| parameter_array
;
fixed_parameters
: fixed_parameter (',' fixed_parameter)*
;
fixed_parameter
: attributes? parameter_modifier? type identifier default_argument?
;
default_argument
: '=' expression
;
parameter_modifier
: parameter_mode_modifier
| 'this'
;
parameter_mode_modifier
: 'ref'
| 'out'
| 'in'
;
parameter_array
: attributes? 'params' array_type identifier
;
パラメーター リストは、1 つ以上のコンマ区切りのパラメーターで構成され、最後のパラメーターのみが parameter_arrayになります。
fixed_parameterは、attributes (§22)、省略可能なin
、out
、ref
、またはthis
修飾子、型、identifier;、およびオプションのdefault_argumentで構成されます。 各 fixed_parameter は、指定された名前を持つ特定の型のパラメーターを宣言します。 this
修飾子はメソッドを拡張メソッドとして指定し、非ジェネリックで入れ子になっていない静的クラスの静的メソッドの最初のパラメーターでのみ使用できます。 パラメーターが struct
型または struct
に制約された型パラメーターである場合、 this
修飾子は、 ref
修飾子または in
修飾子と組み合わせることができますが、 out
修飾子は組み合わせません。 拡張メソッドについては、 §15.6.10 で詳しく説明します。 default_argumentを持つfixed_parameterはオプション パラメーターと呼ばれますがdefault_argumentを持たないfixed_parameterは必要なパラメーターです。 必須パラメーターは、 parameter_listの省略可能なパラメーターの後には表示されません。
ref
、out
、またはthis
修飾子を持つパラメーターは、default_argumentを持つことはできません。 入力パラメーターには、 default_argumentが含まれる場合があります。 default_argumentの式は、次のいずれかになります。
- constant_expression
S
が値型new S()
フォームの式S
が値型default(S)
フォームの式
式は、パラメーターの型への ID または null 許容変換によって暗黙的に変換できる必要があります。
実装する部分メソッド宣言 (§15.6.9)、明示的なインターフェイス メンバー実装 (§18.6.2)、単一パラメーター インデクサー宣言 ( §15.9)、または演算子宣言 (§15.10.1) では、引数を省略できる方法でこれらのメンバーを呼び出すことはできませんので、コンパイラは警告を表示する必要があります。
parameter_arrayは、オプションの attributes (§22)、params
修飾子、array_type、および identifier で構成されます。 パラメーター配列は、指定された名前を持つ特定の配列型の 1 つのパラメーターを宣言します。 パラメーター配列の array_type は、1 次元配列型 (§17.2) である必要があります。 メソッド呼び出しでは、パラメーター配列は、指定された配列型の 1 つの引数を指定するか、配列要素型の 0 個以上の引数を指定することを許可します。 パラメーター配列については、 §15.6.2.4 で詳しく説明します。
parameter_arrayは省略可能なパラメーターの後に発生する可能性がありますが、既定値を指定することはできません。parameter_arrayの引数を省略すると、代わりに空の配列が作成されます。
例: さまざまな種類のパラメーターを次に示します。
void M<T>( ref int i, decimal d, bool b = false, bool? n = false, string s = "Hello", object o = null, T t = default(T), params int[] a ) { }
M
のparameter_listでは、i
は必須のref
パラメーターであり、d
は必須の値パラメーターであり、b
、s
、o
、t
は省略可能な値パラメーターであり、a
はパラメーター配列です。end の例
メソッド宣言では、パラメーターと型パラメーター用に個別の宣言空間 (§7.3) が作成されます。 名前は、型パラメーター リストとメソッドのパラメーター リストによって、この宣言空間に導入されます。 メソッドの本体 (存在する場合) は、この宣言空間内で入れ子になっていると見なされます。 メソッド宣言スペースの 2 つのメンバーが同じ名前を持つことはエラーです。
メソッド呼び出し (§12.8.10.2) は、メソッドのパラメーターとローカル変数のその呼び出しに固有のコピーを作成し、呼び出しの引数リストは新しく作成されたパラメーターに値または変数参照を割り当てます。 メソッドの ブロック 内では、パラメーターは simple_name 式 (§12.8.4) 内の識別子によって参照できます。
次の種類のパラメーターが存在します。
- 値パラメーター (§15.6.2.2)。
- 入力パラメーター (§15.6.2.3.2)。
- 出力パラメーター (§15.6.2.3.4)。
- 参照パラメーター (§15.6.2.3.3)。
- パラメーター配列 (§15.6.2.4)。
注: §7.6 で説明されているように、
in
、out
、およびref
修飾子はメソッドのシグネチャの一部ですが、params
修飾子は含まれません。 end note
15.6.2.2 値パラメーター
修飾子なしで宣言されたパラメーターは、 value パラメーターです。 値パラメーターは、メソッド呼び出しで指定された対応する引数から初期値を取得するローカル変数です。
明確な割り当て規則については、 §9.2.5 を参照してください。
メソッド呼び出しの対応する引数は、パラメーター型に暗黙的に変換可能な式 (§10.2) でなければなりません。
メソッドは、値パラメーターに新しい値を割り当てることができます。 このような割り当ては、value パラメーターによって表されるローカル ストレージの場所にのみ影響します。メソッドの呼び出しで指定された実際の引数には影響しません。
15.6.2.3 参照パラメーター
15.6.2.3.1 全般
入力、出力、および参照パラメーターは、 参照パラメーターです。 参照渡しパラメーターはローカル参照変数 (§9.7) です。最初の参照先は、メソッド呼び出しで指定された対応する引数から取得されます。
注: 参照渡しパラメーターの参照先は、ref 代入 (
= ref
) 演算子を使用して変更できます。
パラメーターが参照渡しパラメーターの場合、メソッド呼び出しの対応する引数は、対応するキーワード、 in
、 ref
、または out
の後に、パラメーターと同じ型の variable_reference (§9.5) で構成されます。 ただし、パラメーターが in
パラメーターの場合、引数は expression その引数式から対応するパラメーターの型への暗黙的な変換 (§10.2) が存在する場合があります。
反復子 (§15.14) または非同期関数 (§15.15) として宣言された関数では、参照パラメーターは使用できません。
複数の参照パラメーターを受け取るメソッドでは、複数の名前が同じストレージの場所を表す可能性があります。
15.6.2.3.2 入力パラメーター
in
修飾子で宣言されたパラメーターは、input パラメーターです。 入力パラメーターに対応する引数は、メソッド呼び出しの時点に存在する変数か、メソッド呼び出しの実装 (§12.6.2.3) によって作成された変数です。 明確な割り当てルールについては、 §9.2.8 を参照してください。
入力パラメーターの値を変更するのはコンパイル時エラーです。
注: 入力パラメーターの主な目的は、効率性を目的としたものです。 メソッド パラメーターの型が (メモリ要件の観点から) 大きな構造体である場合、メソッドを呼び出すときに引数の値全体をコピーしないようにすると便利です。 入力パラメーターを使用すると、メソッドはメモリ内の既存の値を参照しながら、それらの値に対する望ましくない変更に対する保護を提供できます。 end note
15.6.2.3.3 参照パラメーター
ref
修飾子で宣言されたパラメーターは、参照パラメーターです。 明確な割り当てルールについては、 §9.2.6 を参照してください。
例: 例
class Test { static void Swap(ref int x, ref int y) { int temp = x; x = y; y = temp; } static void Main() { int i = 1, j = 2; Swap(ref i, ref j); Console.WriteLine($"i = {i}, j = {j}"); } }
この例では、次のように出力されます。
i = 2, j = 1
Main
でのSwap
の呼び出しでは、x
はi
を表し、y
はj
を表します。 したがって、呼び出しは、i
とj
の値をスワップする効果があります。end の例
例: 次のコード内
class A { string s; void F(ref string a, ref string b) { s = "One"; a = "Two"; b = "Three"; } void G() { F(ref s, ref s); } }
G
でのF
の呼び出しは、a
とb
の両方のs
への参照を渡します。 したがって、その呼び出しでは、名前s
、a
、およびb
はすべて同じストレージの場所を参照し、3 つの割り当てはすべてインスタンス フィールドs
を変更します。end の例
struct
型の場合、インスタンス メソッド、インスタンス アクセサー (§12.2.1)、またはコンストラクター初期化子を持つインスタンス コンストラクターの場合、this
キーワードは構造体型 (§12.8.14) の参照パラメーターとまったく同じように動作します。
15.6.2.3.4 出力パラメーター
out
修飾子で宣言されたパラメーターは、出力パラメーターです。 明確な割り当てルールについては、 §9.2.7 を参照してください。
部分メソッド (§15.6.9) として宣言されたメソッドには、出力パラメーターは含まれません。
注: 出力パラメーターは、通常、複数の戻り値を生成するメソッドで使用されます。 end note
例:
class Test { static void SplitPath(string path, out string dir, out string name) { int i = path.Length; while (i > 0) { char ch = path[i - 1]; if (ch == '\\' || ch == '/' || ch == ':') { break; } i--; } dir = path.Substring(0, i); name = path.Substring(i); } static void Main() { string dir, name; SplitPath(@"c:\Windows\System\hello.txt", out dir, out name); Console.WriteLine(dir); Console.WriteLine(name); } }
この例では、次の出力が生成されます。
c:\Windows\System\ hello.txt
dir
変数とname
変数は、SplitPath
に渡される前に割り当てを解除でき、呼び出し後に確実に割り当てられていると見なされることに注意してください。end の例
15.6.2.4 パラメーター配列
params
修飾子で宣言されたパラメーターは、パラメーター配列です。 パラメーター リストにパラメーター配列が含まれている場合、リスト内の最後のパラメーターであり、1 次元配列型である必要があります。
例:
string[]
型とstring[][]
型はパラメーター配列の型として使用できますが、型string[,]
使用できません。 end の例
注:
params
修飾子を修飾子in
、out
、またはref
と組み合わせすることはできません。 end note
パラメーター配列を使用すると、メソッド呼び出しで次の 2 つの方法のいずれかで引数を指定できます。
- パラメーター配列に指定される引数は、パラメーター配列型に暗黙的に変換可能な単一の式 (§10.2) にすることができます。 この場合、パラメーター配列は値パラメーターとまったく同じように動作します。
- また、呼び出しでは、パラメーター配列に対して 0 個以上の引数を指定できます。各引数は、パラメーター配列の要素型に暗黙的に変換可能な式 (§10.2) です。 この場合、呼び出しは引数の数に対応する長さのパラメーター配列型のインスタンスを作成し、指定された引数値を使用して配列インスタンスの要素を初期化し、新しく作成された配列インスタンスを実際の引数として使用します。
呼び出しで可変数の引数を許可する場合を除き、パラメーター配列は同じ型の値パラメーター (§15.6.2.2) と正確に等しくなります。
例: 例
class Test { static void F(params int[] args) { Console.Write($"Array contains {args.Length} elements:"); foreach (int i in args) { Console.Write($" {i}"); } Console.WriteLine(); } static void Main() { int[] arr = {1, 2, 3}; F(arr); F(10, 20, 30, 40); F(); } }
この例では、次のように出力されます。
Array contains 3 elements: 1 2 3 Array contains 4 elements: 10 20 30 40 Array contains 0 elements:
F
の最初の呼び出しでは、配列arr
を値パラメーターとして渡すだけです。 F の 2 回目の呼び出しでは、指定された要素値を持つ 4 要素int[]
が自動的に作成され、その配列インスタンスが値パラメーターとして渡されます。 同様に、F
の 3 番目の呼び出しでは、0 要素int[]
が作成され、そのインスタンスが値パラメーターとして渡されます。 2 番目と 3 番目の呼び出しは、次の記述とまったく同じです。F(new int[] {10, 20, 30, 40}); F(new int[] {});
end の例
オーバーロード解決を実行する場合、パラメーター配列を持つメソッドは、通常の形式または展開形式 (§12.6.4.2) で適用できます。 メソッドの展開形式は、メソッドの通常の形式が適用できない場合、および展開されたフォームと同じシグネチャを持つ適用可能なメソッドがまだ同じ型で宣言されていない場合にのみ使用できます。
例: 例
class Test { static void F(params object[] a) => Console.WriteLine("F(object[])"); static void F() => Console.WriteLine("F()"); static void F(object a0, object a1) => Console.WriteLine("F(object,object)"); static void Main() { F(); F(1); F(1, 2); F(1, 2, 3); F(1, 2, 3, 4); } }
この例では、次のように出力されます。
F() F(object[]) F(object,object) F(object[]) F(object[])
この例では、パラメーター配列を持つメソッドの 2 つの拡張形式が、通常のメソッドとして既にクラスに含まれています。 したがって、これらの拡張フォームはオーバーロード解決を実行する際には考慮されず、1 番目と 3 番目のメソッドの呼び出しでは通常のメソッドが選択されます。 クラスがパラメーター配列を持つメソッドを宣言する場合、展開されたフォームの一部を通常のメソッドとして含めるのも珍しくありません。 そうすることで、パラメーター配列を持つメソッドの拡張形式が呼び出されたときに発生する配列インスタンスの割り当てを回避できます。
end の例
配列は参照型であるため、パラメーター配列に渡される値を
null
できます。例: 例:
class Test { static void F(params string[] array) => Console.WriteLine(array == null); static void Main() { F(null); F((string) null); } }
この例では、次のように出力されます。
True False
2 番目の呼び出しでは、
F(new string[] { null })
と同等のFalse
が生成され、1 つの null 参照を含む配列が渡されます。end の例
パラメーター配列の型が object[]
されると、メソッドの通常の形式と、単一の object
パラメーターの展開フォームの間にあいまいさが生じる可能性があります。 あいまいさの理由は、 object[]
自体が暗黙的に型 object
に変換可能であるためです。 ただし、必要に応じてキャストを挿入することで解決できるため、あいまいさは問題ありません。
例: 例
class Test { static void F(params object[] args) { foreach (object o in args) { Console.Write(o.GetType().FullName); Console.Write(" "); } Console.WriteLine(); } static void Main() { object[] a = {1, "Hello", 123.456}; object o = a; F(a); F((object)a); F(o); F((object[])o); } }
この例では、次のように出力されます。
System.Int32 System.String System.Double System.Object[] System.Object[] System.Int32 System.String System.Double
F
の最初と最後の呼び出しでは、引数の型からパラメーター型への暗黙的な変換が存在するため、通常の形式のF
が適用されます (両方ともobject[]
型です)。 したがって、オーバーロードの解決では通常の形式のF
が選択され、引数は通常の値パラメーターとして渡されます。 2 番目と 3 番目の呼び出しでは、引数の型からパラメーター型への暗黙的な変換が存在しないため、F
の通常の形式は適用できません (型object
object[]
に暗黙的に変換することはできません)。 ただし、F
の展開形式が適用されるため、オーバーロードの解決によって選択されます。 その結果、呼び出しによって 1 要素object[]
が作成され、配列の 1 つの要素が指定された引数値 (それ自体はobject[]
への参照) で初期化されます。end の例
15.6.3 静的メソッドとインスタンス メソッド
メソッド宣言に static
修飾子が含まれている場合、そのメソッドは静的メソッドと言われます。 static
修飾子が存在しない場合、メソッドはインスタンス メソッドと言われます。
静的メソッドは特定のインスタンスでは動作せず、静的メソッドで this
を参照するのはコンパイル時エラーです。
インスタンス メソッドはクラスの特定のインスタンスで動作し、そのインスタンスは this
としてアクセスできます (§12.8.14)。
静的メンバーとインスタンス メンバーの違いについては、 §15.3.8 で詳しく説明します。
15.6.4 仮想メソッド
インスタンス メソッドの宣言に仮想修飾子が含まれている場合、そのメソッドは 仮想メソッドと言。 仮想修飾子が存在しない場合、このメソッドは 非仮想メソッドと言われます。
非仮想メソッドの実装は不変です。実装は、宣言されているクラスのインスタンスまたは派生クラスのインスタンスでメソッドが呼び出されるかどうかに関係なく同じです。 これに対し、仮想メソッドの実装は派生クラスに置き換えることができます。 継承された仮想メソッドの実装を超越するプロセスは、そのメソッド (§15.6.5) を仮想化と呼ばれます。
仮想メソッド呼び出しでは、その呼び出しが行われるインスタンスの 実行時の型 によって、呼び出す実際のメソッド実装が決定されます。 非仮想メソッド呼び出しでは、インスタンスの compile-time 型 が決定要因になります。 正確に言うと、N
という名前のメソッドが、コンパイル時の型C
と実行時型R
(R
がC
またはC
から派生したクラス) を持つインスタンスにA
引数リストを使用して呼び出されると、呼び出しは次のように処理されます。
- バインド時に、オーバーロードの解決が
C
、N
、およびA
に適用され、C
で宣言および継承された一連のメソッドからM
特定のメソッドを選択します。 これについては、 §12.8.10.2 で説明されています。 - その後、実行時に次の手順を実行します。
M
が非仮想メソッドの場合は、M
が呼び出されます。- それ以外の場合、
M
は仮想メソッドであり、R
に関してM
の最も派生した実装が呼び出されます。
クラスで宣言または継承されるすべての仮想メソッドには、そのクラスに関してメソッド最上位の派生実装が存在します。 R
クラスに関してM
仮想メソッドの最も派生した実装は、次のように決定されます。
R
にM
の仮想宣言の導入が含まれている場合、これはR
に関するM
の最も派生した実装です。- それ以外の場合、
R
にM
のオーバーライドが含まれている場合、これはR
に関するM
の最も派生した実装です。 - それ以外の場合、
R
に関するM
の最も派生した実装は、R
の直接基底クラスに関するM
の最も派生した実装と同じです。
例: 次の例は、仮想メソッドと非仮想メソッドの違いを示しています。
class A { public void F() => Console.WriteLine("A.F"); public virtual void G() => Console.WriteLine("A.G"); } class B : A { public new void F() => Console.WriteLine("B.F"); public override void G() => Console.WriteLine("B.G"); } class Test { static void Main() { B b = new B(); A a = b; a.F(); b.F(); a.G(); b.G(); } }
この例では、
A
では、非仮想メソッドF
と仮想メソッドG
が導入されています。 このクラスB
は、new非仮想メソッドF
を導入するため、継承されたF
を継承されたメソッドG
します。 この例では、次の出力が生成されます。A.F B.F B.G B.G
ステートメント
a.G()
A.G
ではなく、B.G
が呼び出されていることに注意してください。 これは、インスタンスのコンパイル時の型 (A
) ではなく、インスタンスのランタイム型 (B
) によって、呼び出す実際のメソッドの実装が決定されるためです。end の例
メソッドは継承されたメソッドを非表示にできるため、クラスに同じシグネチャを持つ複数の仮想メソッドを含めることができます。 最も派生したメソッドを除くすべてのメソッドが非表示になっているため、あいまいさの問題はありません。
例: 次のコード内
class A { public virtual void F() => Console.WriteLine("A.F"); } class B : A { public override void F() => Console.WriteLine("B.F"); } class C : B { public new virtual void F() => Console.WriteLine("C.F"); } class D : C { public override void F() => Console.WriteLine("D.F"); } class Test { static void Main() { D d = new D(); A a = d; B b = d; C c = d; a.F(); b.F(); c.F(); d.F(); } }
C
クラスとD
クラスには、同じシグネチャを持つ 2 つの仮想メソッド (A
によって導入されたものと、C
によって導入されたもの) が含まれています。C
によって導入されたメソッドは、A
から継承されたメソッドを非表示にします。 したがって、D
のオーバーライド宣言は、C
によって導入されたメソッドをオーバーライドし、D
がA
によって導入されたメソッドをオーバーライドすることはできません。 この例では、次の出力が生成されます。B.F B.F D.F D.F
非表示の仮想メソッドを呼び出すには、メソッドが非表示ではない派生型を介して
D
のインスタンスにアクセスできます。end の例
15.6.5 メソッドをオーバーライドする
インスタンス メソッドの宣言に override
修飾子が含まれている場合、メソッドは override メソッドと言われます。 オーバーライド メソッドは、継承された仮想メソッドを同じシグネチャでオーバーライドします。 仮想メソッド宣言introduces新しいメソッドであるのに対し、オーバーライド メソッド宣言そのメソッドの新しい実装を提供することで、既存の継承された仮想メソッドを指定します。
オーバーライド宣言によってオーバーライドされるメソッドは、overridden 基本メソッドと呼ばれますクラス C
で宣言M
オーバーライド メソッドの場合、 オーバーライドされた基本メソッドは、C
の各基底クラスを調べ、C
の直接基底クラスから始まり、連続する各直接基底クラスを続行することによって決定されます。これは、特定の基底クラスの型で、型引数の置換後にM
と同じシグネチャを持つ少なくとも 1 つのアクセス可能なメソッドが見つかるまで続けられます。 オーバーライドされた基本メソッドを検索する目的で、メソッドが public
されている場合、 protected
されている場合、 protected internal
されている場合、またはメソッドが internal
または private protected
であり、 C
と同じプログラムで宣言されている場合は、アクセス可能と見なされます。
オーバーライド宣言で次のすべてが当てはまる場合を除き、コンパイル時エラーが発生します。
- オーバーライドされた基本メソッドは、前述のように配置できます。
- このようなオーバーライドされた基本メソッドが 1 つだけ存在します。 この制限は、基底クラスの型が構築された型であり、型引数の置換によって 2 つのメソッドのシグネチャが同じになる場合にのみ有効です。
- オーバーライドされた基本メソッドは、仮想メソッド、抽象メソッド、またはオーバーライド メソッドです。 つまり、オーバーライドされた基本メソッドを静的または非仮想にすることはできません。
- オーバーライドされた基本メソッドは、シールメソッドではありません。
- オーバーライドされた基本メソッドの戻り値の型とオーバーライド メソッドの間には ID 変換があります。
- オーバーライド宣言とオーバーライドされた基本メソッドには、同じ宣言されたアクセシビリティがあります。 つまり、オーバーライド宣言では、仮想メソッドのアクセシビリティを変更できません。 ただし、オーバーライドされた基本メソッドが内部で保護されていて、オーバーライド宣言を含むアセンブリとは異なるアセンブリで宣言されている場合は、オーバーライド宣言の宣言されたアクセシビリティが保護されます。
- オーバーライド宣言では、 type_parameter_constraints_clauseは指定されません。 代わりに、制約はオーバーライドされた基本メソッドから継承されます。 オーバーライドされたメソッドの型パラメーターである制約は、継承された制約の型引数に置き換えることができます。 これにより、値型やシール型など、明示的に指定した場合に無効な制約が発生する可能性があります。
例: ジェネリック クラスに対してオーバーライドルールがどのように機能するかを次に示します。
abstract class C<T> { public virtual T F() {...} public virtual C<T> G() {...} public virtual void H(C<T> x) {...} } class D : C<string> { public override string F() {...} // Ok public override C<string> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<string> } class E<T,U> : C<U> { public override U F() {...} // Ok public override C<U> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<U> }
end の例
オーバーライド宣言は、 base_access (§12.8.15) を使用して、オーバーライドされた基本メソッドにアクセスできます。
例: 次のコード内
class A { int x; public virtual void PrintFields() => Console.WriteLine($"x = {x}"); } class B : A { int y; public override void PrintFields() { base.PrintFields(); Console.WriteLine($"y = {y}"); } }
B
のbase.PrintFields()
呼び出しは、A
で宣言された PrintFields メソッドを呼び出します。 base_accessは、仮想呼び出しメカニズムを無効にし、基本メソッドを非virtual
メソッドとして扱うだけです。B
の呼び出しが((A)this).PrintFields()
に記述されている場合、PrintFields
は仮想であり、((A)this)
の実行時の型がB
であるため、A
で宣言されたメソッドではなく、B
で宣言されたPrintFields
メソッドを再帰的に呼び出します。end の例
override
修飾子を含めた場合にのみ、メソッドは別のメソッドをオーバーライドできます。 それ以外のすべての場合、継承されたメソッドと同じシグネチャを持つメソッドは、継承されたメソッドを非表示にします。
例: 次のコード内
class A { public virtual void F() {} } class B : A { public virtual void F() {} // Warning, hiding inherited F() }
B
のF
メソッドにはoverride
修飾子が含まれていないため、A
のF
メソッドはオーバーライドされません。 代わりに、B
のF
メソッドはA
内のメソッドを非表示にし、宣言に新しい修飾子が含まれていないため警告が報告されます。end の例
例: 次のコード内
class A { public virtual void F() {} } class B : A { private new void F() {} // Hides A.F within body of B } class C : B { public override void F() {} // Ok, overrides A.F }
B
のF
メソッドは、A
から継承された仮想F
メソッドを非表示にします。B
の新しいF
にはプライベート アクセスがあるため、そのスコープにはB
のクラス本体のみが含まれており、C
には拡張されません。 したがって、C
内のF
の宣言は、A
から継承されたF
をオーバーライドできます。end の例
15.6.6 シールメソッド
インスタンス メソッドの宣言に sealed
修飾子が含まれている場合、そのメソッドは 封印されたメソッドと言われます。 sealed メソッドは、継承された仮想メソッドを同じシグネチャでオーバーライドします。 シールされたメソッドも、 override
修飾子でマークする必要があります。 sealed
修飾子を使用すると、派生クラスがメソッドをさらにオーバーライドできなくなります。
例: 例
class A { public virtual void F() => Console.WriteLine("A.F"); public virtual void G() => Console.WriteLine("A.G"); } class B : A { public sealed override void F() => Console.WriteLine("B.F"); public override void G() => Console.WriteLine("B.G"); } class C : B { public override void G() => Console.WriteLine("C.G"); }
クラス
B
には、sealed
修飾子を持つF
メソッドと、含まないG
メソッドの 2 つのオーバーライド メソッドが用意されています。B
sealed
修飾子を使用すると、C
がF
をさらにオーバーライドできなくなります。end の例
15.6.7 抽象メソッド
インスタンス メソッド宣言に abstract
修飾子が含まれている場合、そのメソッドは abstract メソッドと言われます。 抽象メソッドは暗黙的に仮想メソッドでもありますが、修飾子を virtual
することはできません。
抽象メソッド宣言では、新しい仮想メソッドが導入されますが、そのメソッドの実装は提供されません。 代わりに、そのメソッドをオーバーライドして独自の実装を提供するには、非抽象派生クラスが必要です。 抽象メソッドは実際の実装を提供しないため、抽象メソッドのメソッド本体は単にセミコロンで構成されます。
抽象メソッド宣言は抽象クラスでのみ許可されます (§15.2.2.2)。
例: 次のコード内
public abstract class Shape { public abstract void Paint(Graphics g, Rectangle r); } public class Ellipse : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r); } public class Box : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r); }
Shape
クラスは、自身を描画できる幾何学的形状オブジェクトの抽象概念を定義します。 意味のある既定の実装がないため、Paint
メソッドは抽象です。Ellipse
クラスとBox
クラスは、具象Shape
実装です。 これらのクラスは非抽象であるため、Paint
メソッドをオーバーライドし、実際の実装を提供する必要があります。end の例
抽象メソッドを参照する base_access (§12.8.15) のコンパイル時エラーです。
例: 次のコード内
abstract class A { public abstract void F(); } class B : A { // Error, base.F is abstract public override void F() => base.F(); }
抽象メソッドを参照しているため、
base.F()
呼び出しのコンパイル時エラーが報告されます。end の例
抽象メソッド宣言は、仮想メソッドをオーバーライドできます。 これにより、抽象クラスは派生クラスでメソッドの再実装を強制し、メソッドの元の実装を使用できなくなります。
例: 次のコード内
class A { public virtual void F() => Console.WriteLine("A.F"); } abstract class B: A { public abstract override void F(); } class C : B { public override void F() => Console.WriteLine("C.F"); }
クラス
A
は仮想メソッドを宣言し、クラスB
は抽象メソッドでこのメソッドをオーバーライドし、クラスC
は抽象メソッドをオーバーライドして独自の実装を提供します。end の例
15.6.8 外部メソッド
メソッド宣言に extern
修飾子が含まれている場合、メソッドは external メソッドと言われます。 外部メソッドは外部で実装され、通常は C# 以外の言語を使用します。 外部メソッド宣言は実際の実装を提供しないため、外部メソッドのメソッド本体は単にセミコロンで構成されます。 外部メソッドをジェネリックにすることはできません。
外部メソッドへのリンケージを実現するメカニズムは、実装によって定義されます。
例:
extern
修飾子とDllImport
属性の使用例を次に示します。class Path { [DllImport("kernel32", SetLastError=true)] static extern bool CreateDirectory(string name, SecurityAttribute sa); [DllImport("kernel32", SetLastError=true)] static extern bool RemoveDirectory(string name); [DllImport("kernel32", SetLastError=true)] static extern int GetCurrentDirectory(int bufSize, StringBuilder buf); [DllImport("kernel32", SetLastError=true)] static extern bool SetCurrentDirectory(string name); }
end の例
15.6.9 部分メソッド
メソッド宣言に partial
修飾子が含まれている場合、そのメソッドは partial メソッドと言われます。 部分メソッドは、部分型 (§15.2.7) のメンバーとしてのみ宣言でき、さまざまな制限が適用されます。
部分メソッドは、型宣言の 1 つの部分で定義し、別の部分で実装できます。 実装は省略可能です。部分が部分メソッドを実装していない場合、部分メソッド宣言とそのすべての呼び出しは、部分の組み合わせによって生成される型宣言から削除されます。
部分メソッドでは、アクセス修飾子を定義しないでください。これらは暗黙的にプライベートです。 戻り値の型は void
し、パラメーターは出力パラメーターにすることはできません。 識別子部分は、void
キーワードの直前に出現する場合にのみ、メソッド宣言でコンテキスト キーワード (§6.4.4) として認識されます。 部分メソッドは、インターフェイス メソッドを明示的に実装できません。
部分メソッド宣言には 2 種類あります。メソッド宣言の本体がセミコロンの場合、宣言は 定義部分メソッド宣言と言われます。 本体がセミコロン以外の場合、宣言は 実装する部分メソッド宣言と言われます。 型宣言の各部分で、特定のシグネチャを持つ部分メソッド宣言を定義するものは 1 つだけであり、特定のシグネチャを持つ部分メソッド宣言の実装は 1 つだけである可能性があります。 実装する部分メソッド宣言が指定されている場合は、対応する定義部分メソッド宣言が存在し、宣言は次に示すように一致する必要があります。
- 宣言には、同じ修飾子 (必ずしも同じ順序とは限りません)、メソッド名、型パラメーターの数、およびパラメーターの数を持つ必要があります。
- 宣言内の対応するパラメーターには、同じ修飾子 (必ずしも同じ順序ではない) と同じ型、または ID 変換可能型 (型パラメーター名の剰余の違い) があります。
- 宣言内の対応する型パラメーターは、同じ制約を持つ必要があります (型パラメーター名の剰余の違い)。
実装する部分メソッド宣言は、対応する定義部分メソッド宣言と同じ部分に含めることができます。
定義する部分メソッドのみがオーバーロードの解決に参加します。 したがって、実装宣言が与えられているかどうかにかかわらず、呼び出し式は部分メソッドの呼び出しに解決される可能性があります。 部分メソッドは常に void
を返すので、このような呼び出し式は常に式ステートメントになります。 さらに、部分メソッドは暗黙的に private
されるため、このようなステートメントは常に、部分メソッドが宣言されている型宣言のいずれかの部分内で発生します。
注: 部分メソッド宣言の定義と実装に一致する定義では、一致するパラメーター名は必要ありません。 これにより、名前付き引数 (§12.6.2.1) が使用される場合、定義発生する可能性があります。 たとえば、1 つのファイルで
M
の部分メソッド宣言を定義し、別のファイルで部分メソッド宣言を実装するとします。// File P1.cs: partial class P { static partial void M(int x); } // File P2.cs: partial class P { static void Caller() => M(y: 0); static partial void M(int y) {} }
は invalid です 呼び出しでは、定義する部分メソッド宣言ではなく、実装からの引数名が使用されます。
end note
部分型宣言の一部に特定の部分メソッドの実装宣言が含まれていない場合、それを呼び出す式ステートメントは、結合された型宣言から単に削除されます。 したがって、任意の部分式を含む呼び出し式は、実行時には効果がありません。 部分メソッド自体も削除され、結合された型宣言のメンバーになりません。
特定の部分メソッドに実装宣言が存在する場合、部分メソッドの呼び出しは保持されます。 部分メソッドは、次を除き、実装する部分メソッド宣言と同様のメソッド宣言を生み出します。
partial
修飾子は含まれません。結果のメソッド宣言の属性は、定義するメソッド宣言と実装する部分メソッド宣言の組み合わせ属性を指定しない順序で指定します。 重複は削除されません。
結果のメソッド宣言のパラメーターの属性は、定義の対応するパラメーターの結合属性であり、実装する部分メソッド宣言は指定されていない順序で行われます。 重複は削除されません。
部分的なメソッド M
に対して定義宣言が指定されているが実装宣言が与えられない場合は、次の制限が適用されます。
M
からデリゲートを作成するのはコンパイル時エラーです (§12.8.17.6)。式ツリー型 (§8.6) に変換される匿名関数内の
M
を参照するのはコンパイル時エラーです。M
の呼び出しの一部として発生する式は、明確な代入状態 (§9.4) には影響しません。これは、コンパイル時エラーにつながる可能性があります。M
をアプリケーションのエントリ ポイントにすることはできません (§7.1)。
部分メソッドは、型宣言の 1 つの部分で、ツールによって生成される部分など、別の部分の動作をカスタマイズできるようにするために役立ちます。 次の部分クラス宣言について考えてみましょう。
partial class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
partial void OnNameChanging(string newName);
partial void OnNameChanged();
}
このクラスが他の部分なしでコンパイルされた場合、定義する部分メソッド宣言とその呼び出しは削除され、結果として得られる結合クラス宣言は次と同じになります。
class Customer
{
string name;
public string Name
{
get => name;
set => name = value;
}
}
ただし、部分メソッドの実装宣言を提供する別の部分を指定するとします。
partial class Customer
{
partial void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
partial void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
その後、結果として得られる結合されたクラス宣言は、次と同じになります。
class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
15.6.10 拡張メソッド
メソッドの最初のパラメーターに this
修飾子が含まれている場合、そのメソッドは extension メソッドと言われます。 拡張メソッドは、非ジェネリックで入れ子になっていない静的クラスでのみ宣言する必要があります。 拡張メソッドの最初のパラメーターは、次のように制限されます。
- 値型を持つ場合にのみ、入力パラメーターにすることができます
- 値型を持っているか、ジェネリック型が構造体に制約されている場合にのみ、参照パラメーターにすることができます
- ポインター型ではありません。
例: 2 つの拡張メソッドを宣言する静的クラスの例を次に示します。
public static class Extensions { public static int ToInt32(this string s) => Int32.Parse(s); public static T[] Slice<T>(this T[] source, int index, int count) { if (index < 0 || count < 0 || source.Length - index < count) { throw new ArgumentException(); } T[] result = new T[count]; Array.Copy(source, index, result, 0, count); return result; } }
end の例
拡張メソッドは、通常の静的メソッドです。 さらに、外側の静的クラスがスコープ内にある場合は、最初の引数として受信側式を使用して、インスタンス メソッド呼び出し構文 (§12.8.10.3) を使用して拡張メソッドを呼び出すことができます。
例: 次のプログラムでは、上記で宣言した拡張メソッドを使用します。
static class Program { static void Main() { string[] strings = { "1", "22", "333", "4444" }; foreach (string s in strings.Slice(1, 2)) { Console.WriteLine(s.ToInt32()); } } }
Slice
メソッドはstring[]
で使用でき、ToInt32
メソッドは拡張メソッドとして宣言されているため、string
で使用できます。 プログラムの意味は、通常の静的メソッド呼び出しを使用して、次と同じです。static class Program { static void Main() { string[] strings = { "1", "22", "333", "4444" }; foreach (string s in Extensions.Slice(strings, 1, 2)) { Console.WriteLine(Extensions.ToInt32(s)); } } }
end の例
15.6.11 メソッド本体
メソッド宣言のメソッド本体は、ブロック本体、式本体、またはセミコロンで構成されます。
抽象メソッド宣言と外部メソッド宣言ではメソッドの実装が提供されないため、メソッド本体はセミコロンで構成されます。 その他のメソッドの場合、メソッド本体はブロック (§13.3) であり、そのメソッドが呼び出されたときに実行するステートメントが含まれます。
メソッドの効果のない戻り値の型は、戻り値の型がvoid
の場合、またはメソッドが非同期で戻り値の型が«TaskType»
場合にvoid
されます (§15.15.1)。 それ以外の場合、非非同期メソッドの有効な戻り値の型はその戻り値の型であり、戻り値の型が «TaskType»<T>
(§15.15.1) の非同期メソッドの有効な戻り値の型は T
。
メソッドの有効な戻り値の型が void
され、メソッドにブロック本体がある場合、ブロック内の return
ステートメント (§13.10.5) は式を指定しません。 void メソッドのブロックの実行が正常に完了した場合 (つまり、メソッド本体の末尾から制御フロー)、そのメソッドは単にその呼び出し元に戻ります。
メソッドの有効な戻り値の型が void
され、メソッドに式本体がある場合、式 E
は statement_expressionで、本文はフォーム { E; }
のブロック本体とまったく同じです。
値による戻り値メソッド (§15.6.1) の場合、そのメソッドの本体の各 return ステートメントでは、有効な戻り値の型に暗黙的に変換できる式を指定する必要があります。
returns-by-ref メソッド (§15.6.1) の場合、そのメソッドの本体内の各 return ステートメントは、有効な戻り値の型であり、caller-context (§9.7.2 のref-safe-contextを持つ式を指定する必要があります。
値による戻り値メソッドと returns by-ref メソッドの場合、メソッド本体のエンドポイントに到達できません。 つまり、制御はメソッド本体の末尾から流れ出すのを許可されていません。
例: 次のコード内
class A { public int F() {} // Error, return value required public int G() { return 1; } public int H(bool b) { if (b) { return 1; } else { return 0; } } public int I(bool b) => b ? 1 : 0; }
メソッド
F
値を返す場合、制御がメソッド本体の末尾から流れ出る可能性があるため、コンパイル時エラーが発生します。 すべての実行可能な実行パスは戻り値を指定する return ステートメントで終わるため、G
メソッドとH
メソッドは正しいです。I
メソッドは正しいです。これは、本体が 1 つの return ステートメントを含むブロックに相当するためです。end の例
15.7 プロパティ
15.7.1 全般
プロパティは、オブジェクトまたはクラスの特性へのアクセスを提供するメンバーです。 プロパティの例としては、文字列の長さ、フォントのサイズ、ウィンドウのキャプション、顧客の名前などがあります。 プロパティは、フィールドの自然な拡張です。どちらも、関連付けられた型を持つ名前付きメンバーであり、フィールドとプロパティにアクセスするための構文は同じです。 ただし、フィールドとは異なり、プロパティは格納場所を表しません。 その代わりに、プロパティには、値の読み取りまたは書き込みの際に実行されるステートメントを指定する "アクセサー" があります。 したがって、プロパティは、オブジェクトまたはクラスの特性の読み取りと書き込みにアクションを関連付けするためのメカニズムを提供します。さらに、そのような特性の計算が可能になります。
プロパティは、 property_declarationを使用して宣言されます。
property_declaration
: attributes? property_modifier* type member_name property_body
| attributes? property_modifier* ref_kind type member_name ref_property_body
;
property_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
property_body
: '{' accessor_declarations '}' property_initializer?
| '=>' expression ';'
;
property_initializer
: '=' variable_initializer ';'
;
ref_property_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
property_declarationには次の 2 種類があります。
- 1 つ目は、ref 以外の値を持つプロパティを宣言します。 その値の型は type です。 この種類のプロパティは、読み取り可能または書き込み可能な場合があります。
- 2 つ目は、ref 値プロパティを宣言します。 その値は、type 型の変数に
readonly
できるvariable_reference (§9.5) です。 この種類のプロパティは読み取り専用です。
property_declarationには、一連の attributes (§22) と、許可されている種類の宣言されたアクセシビリティ (§15.3.6)、new
(§15.3.5) を含めることができます。 static
(§15.7.2),virtual
(§15.6.4, §15.7.6), override
(§15.6.5, §15.7.6)、sealed
(§15.6.6)、abstract
(§15.6.7、 §15.7.6)、および extern
(§15.6.8) 修飾子。
プロパティ宣言は、修飾子の有効な組み合わせに関して、メソッド宣言 (§15.6) と同じ規則に従います。
member_name (§15.6.1) は、プロパティの名前を指定します。 プロパティが明示的なインターフェイス メンバー実装でない限り、 member_name は単に identifier です。 明示的なインターフェイス メンバー実装 (§18.6.2) の場合、 member_name は interface_type の後に ".
" と identifier で構成されます。
プロパティの type は、少なくともプロパティ自体と同じくらいアクセス可能である必要があります (§7.5.5)。
property_bodyは、ステートメント本体または式本体で構成できます。 ステートメント本体では、 accessor_declarationsは "{
" トークンと "}
" トークンで囲まれ、プロパティのアクセサー (§15.7.3) を宣言します。 アクセサーは、プロパティの読み取りと書き込みに関連付けられている実行可能ステートメントを指定します。
property_bodyでは、=>
とそれに続く式で構成される式本体 E
セミコロンはステートメント本体の{ get { return E; } }
とまったく同じであるため、get アクセサーの結果が 1 つの式によって与えられる読み取り専用プロパティを指定する場合にのみ使用できます。
property_initializerは、自動的に実装されるプロパティ (§15.7.4) に対してのみ指定でき、このようなプロパティの基になるフィールドが、式によって指定された値で初期化。
ref_property_bodyは、ステートメント本体または式本体で構成される場合があります。 ステートメント本体では、 get_accessor_declaration はプロパティの get アクセサー (§15.7.3) を宣言します。 アクセサーは、プロパティの読み取りに関連付けられている実行可能ステートメントを指定します。
ref_property_body、=>
とそれに続くref
で構成される式本体では、variable_reference V
とセミコロンはステートメント本体の{ get { return ref V; } }
とまったく同じです。
注: プロパティにアクセスするための構文はフィールドの構文と同じですが、プロパティは変数として分類されません。 したがって、プロパティが ref 値で、変数参照 (§9.7) を返さない限り、プロパティを
in
、out
、またはref
引数として渡すことができません。 end note
プロパティ宣言に extern
修飾子が含まれている場合、プロパティは external プロパティと言われます。 外部プロパティ宣言は実際の実装を提供しないため、accessor_declarations内の各accessor_bodyはセミコロンにする必要があります。
15.7.2 静的プロパティとインスタンス プロパティ
プロパティ宣言に static
修飾子が含まれている場合、プロパティは 静的プロパティと見なされます。 static
修飾子が存在しない場合、プロパティは instance プロパティと言われます。
静的プロパティは特定のインスタンスに関連付けられていないため、静的プロパティのアクセサーで this
を参照するのはコンパイル時エラーです。
インスタンス プロパティはクラスの特定のインスタンスに関連付けられます。そのインスタンスは、そのプロパティのアクセサーの this
(§12.8.14) としてアクセスできます。
静的メンバーとインスタンス メンバーの違いについては、 §15.3.8 で詳しく説明します。
15.7.3 アクセサー
注: この句は、プロパティ (§15.7) とインデクサー (§15.9) の両方に適用されます。 この句は、プロパティの観点から記述されます。インデクサーの読み取りでは、インデクサー/インデクサーをプロパティ/プロパティに置き換え、 §15.9.2 で指定されたプロパティとインデクサーの違いの一覧を参照。 end note
プロパティの accessor_declarations は、そのプロパティの書き込みまたは読み取りに関連付けられている実行可能ステートメントを指定します。
accessor_declarations
: get_accessor_declaration set_accessor_declaration?
| set_accessor_declaration get_accessor_declaration?
;
get_accessor_declaration
: attributes? accessor_modifier? 'get' accessor_body
;
set_accessor_declaration
: attributes? accessor_modifier? 'set' accessor_body
;
accessor_modifier
: 'protected'
| 'internal'
| 'private'
| 'protected' 'internal'
| 'internal' 'protected'
| 'protected' 'private'
| 'private' 'protected'
;
accessor_body
: block
| '=>' expression ';'
| ';'
;
ref_get_accessor_declaration
: attributes? accessor_modifier? 'get' ref_accessor_body
;
ref_accessor_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
accessor_declarationsは、get_accessor_declaration、set_accessor_declaration、またはその両方で構成されます。 各アクセサー宣言は、省略可能な属性、省略可能な accessor_modifier、トークン get
または set
、その後に accessor_bodyで構成されます。
ref 値プロパティの場合、 ref_get_accessor_declaration は省略可能な属性、省略可能な accessor_modifier、トークン get
、その後に ref_accessor_bodyで構成されます。
accessor_modifierの使用には、次の制限があります。
- accessor_modifierは、インターフェイスまたは明示的なインターフェイス メンバーの実装では使用できません。
override
修飾子を持たないプロパティまたはインデクサーの場合、accessor_modifierは、プロパティまたはインデクサーに get アクセサーと set アクセサーの両方がある場合にのみ許可され、それらのアクセサーのいずれかでのみ許可されます。override
修飾子を含むプロパティまたはインデクサーの場合、アクセサーはオーバーライドされるアクセサーのaccessor_modifier (存在する場合) と一致する必要があります。- accessor_modifierでは、プロパティまたはインデクサー自体の宣言されたアクセシビリティよりも厳密に制限されたアクセシビリティを宣言する必要があります。 正確には、
- プロパティまたはインデクサーに
public
の宣言されたアクセシビリティがある場合、 accessor_modifier によって宣言されるアクセシビリティは、private protected
、protected internal
、internal
、protected
、またはprivate
のいずれかになります。 - プロパティまたはインデクサーに
protected internal
の宣言されたアクセシビリティがある場合、 accessor_modifier によって宣言されるアクセシビリティは、private protected
、protected private
、internal
、protected
、またはprivate
のいずれかになります。 - プロパティまたはインデクサーに
internal
またはprotected
の宣言されたアクセシビリティがある場合、 accessor_modifier によって宣言されるアクセシビリティは、private protected
またはprivate
である必要があります。 - プロパティまたはインデクサーに
private protected
の宣言されたアクセシビリティがある場合、 accessor_modifier によって宣言されたアクセシビリティはprivate
。 - プロパティまたはインデクサーに
private
の宣言されたアクセシビリティがある場合、 accessor_modifier は使用できません。
- プロパティまたはインデクサーに
abstract
プロパティとextern
参照値以外のプロパティの場合、指定された各アクセサーのaccessor_bodyは単にセミコロンです。 非抽象の非 extern プロパティでも、インデクサーではない場合は、セミコロンで指定されたすべてのアクセサーの accessor_body を持つことができます。その場合は、 自動実装プロパティです (§15.7.4)。 自動的に実装されるプロパティには、少なくとも get アクセサーが必要です。 その他の非抽象プロパティ、非 extern プロパティのアクセサーの場合、 accessor_body は次のいずれかになります。
- block対応するアクセサーが呼び出されたときに実行されるステートメントを指定します。
- 式本体。
=>
の後に 式 セミコロンが続き、対応するアクセサーが呼び出されたときに実行される 1 つの式を表します。
abstract
およびextern
参照値プロパティの場合、ref_accessor_bodyは単なるセミコロンです。 その他の非抽象、非 extern プロパティのアクセサーの場合、 ref_accessor_body は次のいずれかになります。
- get アクセサーが呼び出されたときに実行するステートメントを指定する block 。
- 式本体。
=>
の後にref
、 variable_reference 、セミコロンが続きます。 変数参照は、get アクセサーが呼び出されたときに評価されます。
ref 値以外のプロパティの get アクセサーは、プロパティ型の戻り値を持つパラメーターなしのメソッドに対応します。 代入のターゲットを除き、このようなプロパティが式で参照されている場合は、プロパティの値を計算するために get アクセサーが呼び出されます (§12.2.2)。
ref 値以外のプロパティの get アクセサーの本体は、 §15.6.11 で説明されている値を返すメソッドの規則に準拠する必要があります。 特に、get アクセサーの本体内のすべての return
ステートメントでは、プロパティ型に暗黙的に変換できる式を指定する必要があります。 さらに、get アクセサーのエンドポイントには到達できません。
ref 値プロパティの get アクセサーは、プロパティ型の変数に対する variable_reference の戻り値を持つパラメーターなしのメソッドに対応します。 このようなプロパティが式で参照されると、プロパティの variable_reference 値を計算するために get アクセサーが呼び出されます。 その 可変参照他の参照と同様に、読み取り専用でない variable_referenceの場合は、コンテキストで必要に応じて参照される変数を書き込みます。
例: 次の例は、代入のターゲットとしての ref 値プロパティを示しています。
class Program { static int field; static ref int Property => ref field; static void Main() { field = 10; Console.WriteLine(Property); // Prints 10 Property = 20; // This invokes the get accessor, then assigns // via the resulting variable reference Console.WriteLine(field); // Prints 20 } }
end の例
ref 値プロパティの get アクセサーの本体は、 §15.6.11 で説明されている ref 値メソッドの規則に準拠する必要があります。
set アクセサーは、プロパティ型の単一の値パラメーターと void
戻り値の型を持つメソッドに対応します。 set アクセサーの暗黙的なパラメーターには、常に value
という名前が付けられます。 プロパティが代入のターゲット (§12.21) として参照されている場合、または ++
または –-
のオペランド (§12.8.16、 §12.9.6)、set アクセサーは、新しい値を提供する引数 (§12.21.2) で呼び出されます。 set アクセサーの本体は、§15.6.11 で説明されているvoid
メソッドの規則に準拠。 特に、set アクセサー本体の return ステートメントでは、式を指定することはできません。 set アクセサーには暗黙的に value
という名前のパラメーターがあるため、set アクセサー内のローカル変数または定数宣言のコンパイル時エラーで、その名前が付けられます。
get アクセサーと set アクセサーの有無に基づいて、プロパティは次のように分類されます。
- get アクセサーと set アクセサーの両方を含むプロパティは、 read-write プロパティと言。
- get アクセサーのみを持つプロパティは、 読み取り専用プロパティと言われます。 これは、読み取り専用プロパティが割り当てのターゲットになるコンパイル時エラーです。
- set アクセサーのみを持つプロパティは、 書き込み専用プロパティと言われます。 代入のターゲットを除き、式の書き込み専用プロパティを参照するのはコンパイル時エラーです。
注: 前置演算子と後置
++
演算子と--
代入演算子、および複合代入演算子は、新しい値を書き込む前にオペランドの古い値を読み取るので、書き込み専用プロパティには適用できません。 end note
例: 次のコード内
public class Button : Control { private string caption; public string Caption { get => caption; set { if (caption != value) { caption = value; Repaint(); } } } public override void Paint(Graphics g, Rectangle r) { // Painting code goes here } }
Button
コントロールは、パブリックCaption
プロパティを宣言します。 Caption プロパティの get アクセサーは、プライベートcaption
フィールドに格納されているstring
を返します。 set アクセサーは、新しい値が現在の値と異なるかどうかを確認し、その場合は新しい値を格納し、コントロールを再描画します。 多くの場合、プロパティは上記のパターンに従います。get アクセサーは単にprivate
フィールドに格納されている値を返し、set アクセサーはそのprivate
フィールドを変更し、オブジェクトの状態を完全に更新するために必要な追加のアクションを実行します。 上記のButton
クラスを考えると、Caption
プロパティの使用例を次に示します。Button okButton = new Button(); okButton.Caption = "OK"; // Invokes set accessor string s = okButton.Caption; // Invokes get accessor
ここで、set アクセサーはプロパティに値を割り当てることで呼び出され、get アクセサーは式のプロパティを参照することによって呼び出されます。
end の例
プロパティの get アクセサーと set アクセサーは個別のメンバーではなく、プロパティのアクセサーを個別に宣言することはできません。
例: 例
class A { private string name; // Error, duplicate member name public string Name { get => name; } // Error, duplicate member name public string Name { set => name = value; } }
は、単一の読み取り/書き込みプロパティを宣言しません。 代わりに、同じ名前の 2 つのプロパティを宣言します。1 つは読み取り専用で、1 つは書き込み専用です。 同じクラスで宣言された 2 つのメンバーは同じ名前を持つことができないため、この例ではコンパイル時エラーが発生します。
end の例
派生クラスが継承プロパティと同じ名前でプロパティを宣言すると、派生プロパティは読み取りと書き込みの両方に関して継承されたプロパティを非表示にします。
例: 次のコード内
class A { public int P { set {...} } } class B : A { public new int P { get {...} } }
B
のP
プロパティは、読み取りと書き込みの両方に関して、A
のP
プロパティを非表示にします。 したがって、ステートメント内でB b = new B(); b.P = 1; // Error, B.P is read-only ((A)b).P = 1; // Ok, reference to A.P
B
の読み取り専用P
プロパティでは、A
の書き込み専用のP
プロパティが非表示になるため、b.P
への割り当てによってコンパイル時エラーが報告されます。 ただし、キャストを使用して非表示のP
プロパティにアクセスできることに注意してください。end の例
パブリック フィールドとは異なり、プロパティはオブジェクトの内部状態とそのパブリック インターフェイスを分離します。
例:
Point
構造体を使用して場所を表す次のコードを考えてみましょう。class Label { private int x, y; private string caption; public Label(int x, int y, string caption) { this.x = x; this.y = y; this.caption = caption; } public int X => x; public int Y => y; public Point Location => new Point(x, y); public string Caption => caption; }
ここでは、
Label
クラスは、x
とy
の 2 つのint
フィールドを使用してその場所を格納します。 この場所は、X
プロパティとY
プロパティ、およびPoint
型のLocation
プロパティの両方としてパブリックに公開されます。 将来のバージョンのLabel
で、場所をPoint
として内部的に格納する方が便利になった場合は、クラスのパブリック インターフェイスに影響を与えずに変更を加えることができます。class Label { private Point location; private string caption; public Label(int x, int y, string caption) { this.location = new Point(x, y); this.caption = caption; } public int X => location.X; public int Y => location.Y; public Point Location => location; public string Caption => caption; }
x
とy
がフィールドpublic readonly
されていた場合、Label
クラスにこのような変更を加えるのは不可能でした。end の例
注: プロパティを使用して状態を公開することは、必ずしもフィールドを直接公開するよりも効率的ではありません。 特に、プロパティが非仮想であり、少量のコードのみが含まれている場合、実行環境ではアクセサーの呼び出しがアクセサーの実際のコードに置き換えられる可能性があります。 このプロセスは インライン化と呼ばれ、フィールド アクセスと同じくらい効率的なプロパティ アクセスを実現しますが、プロパティの柔軟性が向上します。 end note
例: get アクセサーの呼び出しは概念的にはフィールドの値の読み取りと同等であるため、get アクセサーが観察可能な副作用を持つには不適切なプログラミング スタイルと見なされます。 この例では、
class Counter { private int next; public int Next => next++; }
Next
プロパティの値は、プロパティが以前にアクセスされた回数によって異なります。 したがって、プロパティにアクセスすると観察可能な副作用が発生し、代わりにプロパティをメソッドとして実装する必要があります。get アクセサーの "副作用なし" 規則は、フィールドに格納されている値を返すためだけに get アクセサーを常に記述する必要があることを意味するわけではありません。 実際、get アクセサーは、多くの場合、複数のフィールドにアクセスするかメソッドを呼び出すことによって、プロパティの値を計算します。 ただし、適切に設計された get アクセサーでは、オブジェクトの状態が観察可能な変更を引き起こすアクションは実行されません。
end の例
プロパティを使用すると、リソースが最初に参照されるまでリソースの初期化を遅らせることができます。
例:
public class Console { private static TextReader reader; private static TextWriter writer; private static TextWriter error; public static TextReader In { get { if (reader == null) { reader = new StreamReader(Console.OpenStandardInput()); } return reader; } } public static TextWriter Out { get { if (writer == null) { writer = new StreamWriter(Console.OpenStandardOutput()); } return writer; } } public static TextWriter Error { get { if (error == null) { error = new StreamWriter(Console.OpenStandardError()); } return error; } } ... }
Console
クラスには、標準の入力、出力、エラー デバイスをそれぞれ表す 3 つのプロパティ (In
、Out
、およびError
) が含まれています。 これらのメンバーをプロパティとして公開することで、Console
クラスは、実際に使用されるまで初期化を遅らせることができます。 たとえば、最初にOut
プロパティを参照すると、次のようになります。Console.Out.WriteLine("hello, world");
出力デバイスの基になる
TextWriter
が作成されます。 ただし、アプリケーションがIn
プロパティとError
プロパティを参照しない場合、それらのデバイスのオブジェクトは作成されません。end の例
15.7.4 自動的に実装されるプロパティ
自動的に実装されるプロパティ (または短い場合は自動プロパティ) は、セミコロンのみの accessor_bodyを持つ非抽象プロパティ、非 extern プロパティ、非 ref 値プロパティです。 自動プロパティには get アクセサーがあり、必要に応じて set アクセサーを持つことができます。
プロパティが自動的に実装されるプロパティとして指定されている場合、非表示のバッキング フィールドはプロパティに対して自動的に使用でき、アクセサーはそのバッキング フィールドとの間で読み取りと書き込みを行うために実装されます。 非表示のバッキング フィールドにはアクセスできません。読み取りおよび書き込みは、自動的に実装されたプロパティ アクセサーを介してのみ、包含型内であっても行うことができます。 自動プロパティに set アクセサーがない場合、バッキング フィールドは readonly
と見なされます (§15.5.3)。 readonly
フィールドと同様に、読み取り専用の自動プロパティも、外側のクラスのコンストラクターの本体に割り当てることができます。 このような割り当ては、プロパティの読み取り専用バッキング フィールドに直接割り当てられます。
自動プロパティには必要に応じて property_initializerがあり、 variable_initializer としてバッキング フィールドに直接適用されます (§17.7)。
例:
public class Point { public int X { get; set; } // Automatically implemented public int Y { get; set; } // Automatically implemented }
は、次の宣言と同じです。
public class Point { private int x; private int y; public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } }
end の例
例: 次の例を参照してください。
public class ReadOnlyPoint { public int X { get; } public int Y { get; } public ReadOnlyPoint(int x, int y) { X = x; Y = y; } }
は、次の宣言と同じです。
public class ReadOnlyPoint { private readonly int __x; private readonly int __y; public int X { get { return __x; } } public int Y { get { return __y; } } public ReadOnlyPoint(int x, int y) { __x = x; __y = y; } }
読み取り専用フィールドへの割り当ては、コンストラクター内で行われるため有効です。
end の例
バッキング フィールドは非表示ですが、そのフィールドには、自動的に実装されるプロパティの property_declaration (§15.7.1) を介してフィールドターゲット属性が直接適用される場合があります。
例: 次のコード
[Serializable] public class Foo { [field: NonSerialized] public string MySecret { get; set; } }
結果として、コードが次のように記述されているかのように、フィールドターゲット属性
NonSerialized
コンパイラによって生成されたバッキング フィールドに適用されます。[Serializable] public class Foo { [NonSerialized] private string _mySecretBackingField; public string MySecret { get { return _mySecretBackingField; } set { _mySecretBackingField = value; } } }
end の例
15.7.5 アクセシビリティ
アクセサーに accessor_modifierがある場合、アクセサーのアクセシビリティ ドメイン (§7.5.3) は、 accessor_modifierの宣言されたアクセシビリティを使用して決定されます。 アクセサーに accessor_modifierがない場合、アクセサーのアクセシビリティ ドメインは、プロパティまたはインデクサーの宣言されたアクセシビリティから決定されます。
accessor_modifierの存在は、メンバー参照 (§12.5) またはオーバーロード解決 (§12.6.4) には影響しません。 プロパティまたはインデクサーの修飾子は、アクセスのコンテキストに関係なく、どのプロパティまたはインデクサーにバインドされるかを常に決定します。
ref 値以外の特定のプロパティまたは ref 値以外のインデクサーが選択されると、関連する特定のアクセサーのアクセシビリティ ドメインを使用して、その使用法が有効かどうかを判断します。
- 使用が値 (§12.2.2) の場合、get アクセサーが存在し、アクセス可能である必要があります。
- 単純な割り当てのターゲットとして使用する場合 (§12.21.2)、set アクセサーが存在し、アクセス可能である必要があります。
- 複合代入のターゲット (§12.21.4) または
++
演算子または--
演算子 (§12.8.16、 §12.9.6) として使用する場合は、get アクセサーと set アクセサーの両方が存在し、アクセス可能である必要があります。
例: 次の例では、set アクセサーのみが呼び出されるコンテキストでも、プロパティ
A.Text
はプロパティB.Text
によって非表示になります。 これに対し、プロパティB.Count
はクラスM
からアクセスできないため、代わりにアクセス可能なプロパティA.Count
が使用されます。class A { public string Text { get => "hello"; set { } } public int Count { get => 5; set { } } } class B : A { private string text = "goodbye"; private int count = 0; public new string Text { get => text; protected set => text = value; } protected new int Count { get => count; set => count = value; } } class M { static void Main() { B b = new B(); b.Count = 12; // Calls A.Count set accessor int i = b.Count; // Calls A.Count get accessor b.Text = "howdy"; // Error, B.Text set accessor not accessible string s = b.Text; // Calls B.Text get accessor } }
end の例
特定の ref 値プロパティまたは ref 値インデクサーが選択されたら、使用法が値、単純代入のターゲット、または複合代入のターゲットのいずれであるか。関連する get アクセサーのアクセシビリティ ドメインは、その使用が有効かどうかを判断するために使用されます。
インターフェイスの実装に使用されるアクセサーには、 accessor_modifierは含まれません。 インターフェイスの実装に使用されるアクセサーが 1 つだけの場合、もう一方のアクセサーは accessor_modifierで宣言できます。
例:
public interface I { string Prop { get; } } public class C : I { public string Prop { get => "April"; // Must not have a modifier here internal set {...} // Ok, because I.Prop has no set accessor } }
end の例
15.7.6 仮想アクセサー、シールアクセサー、オーバーライドアクセサー、および抽象アクセサー
注: この句は、プロパティ (§15.7) とインデクサー (§15.9) の両方に適用されます。 この句は、プロパティの観点から記述されます。インデクサーの読み取りでは、インデクサー/インデクサーをプロパティ/プロパティに置き換え、 §15.9.2 で指定されたプロパティとインデクサーの違いの一覧を参照。 end note
仮想プロパティ宣言は、プロパティのアクセサーが仮想であることを指定します。 virtual
修飾子は、プロパティのすべての非プライベート アクセサーに適用されます。 仮想プロパティのアクセサーに private
accessor_modifierがある場合、プライベート アクセサーは暗黙的に仮想ではありません。
抽象プロパティ宣言は、プロパティのアクセサーが仮想であることを指定しますが、アクセサーの実際の実装は提供しません。 代わりに、非抽象派生クラスは、プロパティをオーバーライドしてアクセサーに独自の実装を提供する必要があります。 抽象プロパティ宣言のアクセサーは実際の実装を提供しないため、その accessor_body は単にセミコロンで構成されます。 抽象プロパティには、 private
アクセサーは含まれません。
abstract
修飾子とoverride
修飾子の両方を含むプロパティ宣言は、プロパティが抽象プロパティであり、基本プロパティをオーバーライドすることを指定します。 このようなプロパティのアクセサーも抽象です。
抽象プロパティ宣言は抽象クラスでのみ許可されます (§15.2.2.2)。 継承された仮想プロパティのアクセサーは、 override
ディレクティブを指定するプロパティ宣言を含めることで、派生クラスでオーバーライドできます。 これは、 プロパティ宣言と呼ばれます。 オーバーライドするプロパティ宣言では、新しいプロパティは宣言されません。 代わりに、既存の仮想プロパティのアクセサーの実装を特殊化するだけです。
オーバーライド宣言とオーバーライドされた基本プロパティは、同じ宣言されたアクセシビリティを持つ必要があります。 つまり、オーバーライド宣言では、基本プロパティのアクセシビリティは変更されません。 ただし、オーバーライドされた基本プロパティが内部で保護されていて、オーバーライド宣言を含むアセンブリとは異なるアセンブリで宣言されている場合は、オーバーライド宣言の宣言されたアクセシビリティが保護されます。 継承されたプロパティにアクセサーが 1 つしかない場合 (つまり、継承されたプロパティが読み取り専用または書き込み専用の場合)、オーバーライドするプロパティには、そのアクセサーのみが含まれます。 継承されたプロパティに両方のアクセサーが含まれている場合 (つまり、継承されたプロパティが読み取り/書き込みの場合)、オーバーライドするプロパティには、1 つのアクセサーまたは両方のアクセサーを含めることができます。 オーバーライドするプロパティの型と継承されたプロパティの間には ID 変換が必要です。
オーバーライドするプロパティ宣言には、 sealed
修飾子を含めることができます。 この修飾子を使用すると、派生クラスがプロパティをさらにオーバーライドできなくなります。 シールプロパティのアクセサーもシールされます。
宣言と呼び出し構文の違いを除き、仮想、シール、オーバーライド、および抽象アクセサーは、仮想、シール、オーバーライド、および抽象メソッドとまったく同じように動作します。 具体的には、 §15.6.4、 §15.6.5、 §15.6.6、および §15.6.7 で説明されている規則は アクセサーが対応するフォームのメソッドであるかのように適用されます。
- get アクセサーは、プロパティ型の戻り値と、包含プロパティと同じ修飾子を持つパラメーターなしのメソッドに対応します。
- set アクセサーは、プロパティ型の単一の値パラメーター、void 戻り値の型、および包含プロパティと同じ修飾子を持つメソッドに対応します。
例: 次のコード内
abstract class A { int y; public virtual int X { get => 0; } public virtual int Y { get => y; set => y = value; } public abstract int Z { get; set; } }
X
は仮想読み取り専用プロパティであり、Y
は仮想読み取り/書き込みプロパティであり、Z
は抽象読み取り/書き込みプロパティです。Z
は抽象であるため、含まれるクラス A も抽象として宣言されます。
A
から派生するクラスを次に示します。class B : A { int z; public override int X { get => base.X + 1; } public override int Y { set => base.Y = value < 0 ? 0: value; } public override int Z { get => z; set => z = value; } }
ここでは、
X
、Y
、およびZ
の宣言は、プロパティ宣言をオーバーライドしています。 各プロパティ宣言は、対応する継承されたプロパティのアクセシビリティ修飾子、型、および名前と完全に一致します。X
の get アクセサーとY
の set アクセサーは、base キーワードを使用して継承されたアクセサーにアクセスします。Z
の宣言は両方の抽象アクセサーをオーバーライドします。したがって、B
に未処理のabstract
関数メンバーはなく、B
は非抽象クラスであることが許可されます。end の例
プロパティがオーバーライドとして宣言されている場合、オーバーライドされたアクセサーは、オーバーライドするコードからアクセスできる必要があります。 さらに、プロパティまたはインデクサー自体とアクセサーの両方の宣言されたアクセシビリティは、オーバーライドされたメンバーとアクセサーのアクセシビリティと一致する必要があります。
例:
public class B { public virtual int P { get {...} protected set {...} } } public class D: B { public override int P { get {...} // Must not have a modifier here protected set {...} // Must specify protected here } }
end の例
15.8 イベント
15.8.1 全般
イベントは、オブジェクトまたはクラスで通知を提供するためのメンバーです。 クライアントは、イベント ハンドラーを指定することで、イベントの実行可能コードをアタッチできます。
イベントは、 event_declarationを使用して宣言されます。
event_declaration
: attributes? event_modifier* 'event' type variable_declarators ';'
| attributes? event_modifier* 'event' type member_name
'{' event_accessor_declarations '}'
;
event_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
event_accessor_declarations
: add_accessor_declaration remove_accessor_declaration
| remove_accessor_declaration add_accessor_declaration
;
add_accessor_declaration
: attributes? 'add' block
;
remove_accessor_declaration
: attributes? 'remove' block
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
An event_declaration may include a set of attributes (§22) and any one of the permitted kinds of declared accessibility (§15.3.6), the new
(§15.3.5), static
(§15.6.3, §15.8.4), virtual
(§15.6.4, §15.8.5), override
(§15.6.5, §15.8.5), sealed
(§15.6.6), abstract
(§15.6.7, §15.8.5), and extern
(§15.6.8) modifiers.
イベント宣言は、修飾子の有効な組み合わせに関して、メソッド宣言 (§15.6) と同じ規則に従います。
イベント宣言の 型 は delegate_type (§8.2.8) であり、 delegate_type は少なくともイベント自体 (§7.5.5) と同じアクセス可能である必要があります。
イベント宣言には、 event_accessor_declarationを含めることができます。 ただし、そうでない場合、非抽象イベントの場合、コンパイラはそれらを自動的に提供します (§15.8.2)。 extern
イベントのアクセサーは外部で提供されます。
event_accessor_declarationを省略するイベント宣言では、variable_declaratorごとに 1 つ以上のイベントが定義されます。 属性と修飾子は、このような event_declarationによって宣言されたすべてのメンバーに適用されます。
abstract
修飾子とevent_accessor_declarationの両方を含めるevent_declarationのコンパイル時エラーです。
イベント宣言に extern
修飾子が含まれている場合、イベントは 外部イベントと言われます。 外部イベント宣言は実際の実装を提供しないため、 extern
修飾子と event_accessor_declarationの両方を含めるのはエラーです。
variable_initializerを含めるabstract
またはexternal
修飾子を持つイベント宣言のvariable_declaratorのコンパイル時エラーです。
イベントは、 +=
演算子および -=
演算子の左オペランドとして使用できます。 これらの演算子はそれぞれ、イベント ハンドラーをアタッチしたり、イベントからイベント ハンドラーを削除したりするために使用され、イベントのアクセス修飾子は、そのような操作が許可されるコンテキストを制御します。
イベントが宣言されている型の外部にあるコードによってイベントに対して許可される操作は、 +=
と -=
だけです。 そのため、このようなコードはイベントのハンドラーを追加および削除できますが、イベント ハンドラーの基になるリストを直接取得または変更することはできません。
フォーム x += y
またはx –= y
の操作では、x
がイベントである場合、操作の結果は型void
(§12.21.5) を持つ (割り当ての後にx
の値を持つx
の型を持つのではなく) 、非イベント型で定義された+=
演算子と-=
演算子)。 これにより、外部コードがイベントの基になるデリゲートを間接的に調べることができなくなります。
例:
Button
クラスのインスタンスにイベント ハンドラーをアタッチする方法を次の例に示します。public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; } public class LoginDialog : Form { Button okButton; Button cancelButton; public LoginDialog() { okButton = new Button(...); okButton.Click += new EventHandler(OkButtonClick); cancelButton = new Button(...); cancelButton.Click += new EventHandler(CancelButtonClick); } void OkButtonClick(object sender, EventArgs e) { // Handle okButton.Click event } void CancelButtonClick(object sender, EventArgs e) { // Handle cancelButton.Click event } }
ここでは、
LoginDialog
インスタンス コンストラクターによって 2 つのButton
インスタンスが作成され、イベント ハンドラーがClick
イベントにアタッチされます。end の例
15.8.2 フィールドに似たイベント
イベントの宣言を含むクラスまたは構造体のプログラム テキスト内で、フィールドのように特定のイベントを使用できます。 この方法で使用する場合、イベントは抽象または例外ではなく、 event_accessor_declarationを明示的に含めてはならない。 このようなイベントは、フィールドを許可する任意のコンテキストで使用できます。 このフィールドにはデリゲート (§20) が含まれています。これは、イベントに追加されたイベント ハンドラーの一覧を参照します。 イベント ハンドラーが追加されていない場合、フィールドには null
が含まれます。
例: 次のコード内
public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; protected void OnClick(EventArgs e) { EventHandler handler = Click; if (handler != null) { handler(this, e); } } public void Reset() => Click = null; }
Click
は、Button
クラス内のフィールドとして使用されます。 この例で示すように、フィールドは、デリゲート呼び出し式で検査、変更、および使用できます。Button
クラスのOnClick
メソッドは、Click
イベントを "発生" させます。 イベントを発生させるという概念は、イベントによって表されるデリゲートの呼び出しとまったく同じです。したがって、イベントを発生させるための特殊な言語コンストラクトはありません。 デリゲートの呼び出しの前に、デリゲートが null 以外であることを確認し、スレッド セーフを確保するためにローカル コピーでチェックが行われていることを確認します。
Button
クラスの宣言の外部では、Click
メンバーは、次のように、+=
演算子と–=
演算子の左側でのみ使用できます。b.Click += new EventHandler(...);
Click
イベントの呼び出しリストにデリゲートを追加します。Click –= new EventHandler(...);
これにより、
Click
イベントの呼び出しリストからデリゲートが削除されます。end の例
フィールドに似たイベントをコンパイルすると、コンパイラは自動的にデリゲートを保持するストレージを作成し、デリゲート フィールドにイベント ハンドラーを追加または削除するイベントのアクセサーを作成します。 追加操作と削除操作はスレッド セーフであり、インスタンス イベントの包含オブジェクトのロック (§13.13) を保持している間、または静的イベントの System.Type
オブジェクト (§12.8.18) を保持している間に実行される場合があります (ただし、必須ではありません)。
注: したがって、フォームのインスタンス イベント宣言は次のようになります。
class X { public event D Ev; }
は、次に相当するものにコンパイルされます。
class X { private D __Ev; // field to hold the delegate public event D Ev { add { /* Add the delegate in a thread safe way */ } remove { /* Remove the delegate in a thread safe way */ } } }
クラス
X
内で、+=
および–=
演算子の左側にあるEv
への参照により、add アクセサーと remove アクセサーが呼び出されます。Ev
への他のすべての参照は、代わりに非表示フィールド__Ev
を参照するようにコンパイルされます (§12.8.7)。 "__Ev
" という名前は任意です。非表示フィールドには任意の名前を付けるか、まったく名前を指定できません。end note
15.8.3 イベント アクセサー
注: イベント宣言では、上記の
Button
例のように、通常、event_accessor_declarationは省略されます。 たとえば、イベントごとに 1 つのフィールドのストレージ コストが許容されない場合に含まれる場合があります。 このような場合、クラスは event_accessor_declarationを含め、イベント ハンドラーの一覧を格納するためのプライベート メカニズムを使用できます。 end note
イベントの event_accessor_declarations では、イベント ハンドラーの追加と削除に関連付けられている実行可能ステートメントを指定します。
アクセサー宣言は、 add_accessor_declaration と remove_accessor_declarationで構成されます。 各アクセサー宣言は、トークンの追加または削除の後に ブロックで構成されます。 add_accessor_declarationに関連付けられているblockは、イベント ハンドラーが追加されたときに実行するステートメントを指定し、remove_accessor_declarationに関連付けられているblockは、イベント ハンドラーが削除されたときに実行するステートメントを指定します。
各 add_accessor_declaration と remove_accessor_declaration は、イベント型の単一の値パラメーターと void
戻り値の型を持つメソッドに対応します。 イベント アクセサーの暗黙的なパラメーターには、 value
という名前が付けられます。 イベントの割り当てでイベントを使用する場合は、適切なイベント アクセサーが使用されます。 具体的には、割り当て演算子が +=
場合は add アクセサーが使用され、代入演算子が –=
場合は remove アクセサーが使用されます。 どちらの場合も、代入演算子の右オペランドがイベント アクセサーの引数として使用されます。 add_accessor_declarationまたはremove_accessor_declarationのブロックは、§15.6.9 で説明されているvoid
メソッドの規則に準拠。 特に、このようなブロック内の return
ステートメントでは、式を指定することはできません。
イベント アクセサーには暗黙的に value
という名前のパラメーターがあるため、イベント アクセサーで宣言されているローカル変数または定数のコンパイル時エラーで、その名前が付けられます。
例: 次のコード内
class Control : Component { // Unique keys for events static readonly object mouseDownEventKey = new object(); static readonly object mouseUpEventKey = new object(); // Return event handler associated with key protected Delegate GetEventHandler(object key) {...} // Add event handler associated with key protected void AddEventHandler(object key, Delegate handler) {...} // Remove event handler associated with key protected void RemoveEventHandler(object key, Delegate handler) {...} // MouseDown event public event MouseEventHandler MouseDown { add { AddEventHandler(mouseDownEventKey, value); } remove { RemoveEventHandler(mouseDownEventKey, value); } } // MouseUp event public event MouseEventHandler MouseUp { add { AddEventHandler(mouseUpEventKey, value); } remove { RemoveEventHandler(mouseUpEventKey, value); } } // Invoke the MouseUp event protected void OnMouseUp(MouseEventArgs args) { MouseEventHandler handler; handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey); if (handler != null) { handler(this, args); } } }
Control
クラスは、イベントの内部ストレージ メカニズムを実装します。AddEventHandler
メソッドはデリゲート値をキーに関連付け、GetEventHandler
メソッドは現在キーに関連付けられているデリゲートを返し、RemoveEventHandler
メソッドは指定されたイベントのイベント ハンドラーとしてデリゲートを削除します。 おそらく、基になるストレージ メカニズムは、null デリゲート値をキーに関連付けるコストが発生しないため、未処理のイベントがストレージを使用しなくなります。end の例
15.8.4 静的イベントとインスタンス イベント
イベント宣言に static
修飾子が含まれている場合、イベントは 静的イベントと言われます。 static
修飾子が存在しない場合、イベントは instance イベントと言われます。
静的イベントは特定のインスタンスに関連付けされておらず、静的イベントのアクセサーで this
を参照するのはコンパイル時エラーです。
インスタンス イベントはクラスの特定のインスタンスに関連付けられます。このインスタンスには、そのイベントのアクセサーで this
(§12.8.14) としてアクセスできます。
静的メンバーとインスタンス メンバーの違いについては、 §15.3.8 で詳しく説明します。
15.8.5 仮想アクセサー、シールアクセサー、オーバーライドアクセサー、および抽象アクセサー
仮想イベント宣言は、そのイベントのアクセサーが仮想であることを指定します。 virtual
修飾子は、イベントの両方のアクセサーに適用されます。
抽象イベント宣言は、イベントのアクセサーが仮想であることを指定しますが、アクセサーの実際の実装は提供しません。 代わりに、非抽象派生クラスは、イベントをオーバーライドすることによってアクセサーに独自の実装を提供する必要があります。 抽象イベント宣言のアクセサーは実際の実装を提供しないため、 event_accessor_declarationを提供しません。
abstract
修飾子とoverride
修飾子の両方を含むイベント宣言は、イベントが抽象イベントであり、基本イベントをオーバーライドすることを指定します。 このようなイベントのアクセサーも抽象です。
抽象イベント宣言は抽象クラスでのみ許可されます (§15.2.2.2)。
継承された仮想イベントのアクセサーは、 override
修飾子を指定するイベント宣言を含めることで、派生クラスでオーバーライドできます。 これは、 管理イベント宣言と呼ばれます。 オーバーライドするイベント宣言では、新しいイベントは宣言されません。 代わりに、既存の仮想イベントのアクセサーの実装を特殊化するだけです。
オーバーライドするイベント宣言では、オーバーライドされたイベントとまったく同じアクセシビリティ修飾子と名前を指定し、オーバーライドするイベントの型とオーバーライドされたイベントの間に ID 変換を行い、add アクセサーと remove アクセサーの両方を宣言内で指定する必要があります。
オーバーライドするイベント宣言には、 sealed
修飾子を含めることができます。 this
修飾子を使用すると、派生クラスがイベントをさらにオーバーライドできなくなります。 シールされたイベントのアクセサーもシールされます。
new
修飾子を含めるには、オーバーライドするイベント宣言のコンパイル時エラーです。
宣言と呼び出し構文の違いを除き、仮想、シール、オーバーライド、および抽象アクセサーは、仮想、シール、オーバーライド、および抽象メソッドとまったく同じように動作します。 具体的には、 §15.6.4、 §15.6.5、 §15.6.6、および §15.6.7 で説明されている規則は アクセサーが対応するフォームのメソッドであるかのように適用されます。 各アクセサーは、イベント型の単一の値パラメーター、 void
戻り値の型、および包含イベントと同じ修飾子を持つメソッドに対応します。
15.9 インデクサー
15.9.1 全般
indexer は、オブジェクトを配列と同じ方法でインデックス付けできるようにするメンバーです。 インデクサーは、 indexer_declarationを使用して宣言されます。
indexer_declaration
: attributes? indexer_modifier* indexer_declarator indexer_body
| attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
;
indexer_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
indexer_declarator
: type 'this' '[' parameter_list ']'
| type interface_type '.' 'this' '[' parameter_list ']'
;
indexer_body
: '{' accessor_declarations '}'
| '=>' expression ';'
;
ref_indexer_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
indexer_declarationには次の 2 種類があります。
- 1 つ目は、ref 以外の値を持つインデクサーを宣言します。 その値の型は type です。 この種のインデクサーは、読み取り可能または書き込み可能な場合があります。
- 2 つ目は、ref 値インデクサーを宣言します。 その値は、type 型の変数に
readonly
できるvariable_reference (§9.5) です。 この種のインデクサーは読み取り専用です。
indexer_declarationには、一連のattributes (§22) と、許可されている種類の宣言されたアクセシビリティ (§15.3.6)、new
(§15.3.5)、virtual
(§15§15) を含めることができます。 .6.4)、override
(§15.6.5)、sealed
(§15.6.6)、abstract
(§15.6.7)、およびextern
(§15.6.8) 修飾子。
インデクサー宣言は、修飾子の有効な組み合わせに関してメソッド宣言 (§15.6) と同じ規則に従います。1 つの例外は、 static
修飾子がインデクサー宣言で許可されないことです。
インデクサー宣言の type は、宣言によって導入されたインデクサーの要素型を指定します。
注: インデクサーは配列要素に似たコンテキストで使用されるように設計されているため、配列に対して定義されている 要素型 という用語もインデクサーと共に使用されます。 end note
インデクサーが明示的なインターフェイス メンバー実装でない限り、 type の後にキーワード this
が続きます。 明示的なインターフェイス メンバー実装の場合、 type の後に、 interface_type、".
"、およびキーワード this
が続きます。 他のメンバーとは異なり、インデクサーにはユーザー定義名がありません。
parameter_listは、インデクサーのパラメーターを指定します。 インデクサーのパラメーター リストは、メソッドのパラメーター リスト (§15.6.2) に対応します。ただし、少なくとも 1 つのパラメーターを指定する必要があり、 this
、 ref
、および out
パラメーター修飾子は許可されません。
インデクサーの type と、 parameter_list で参照される各型は、少なくともインデクサー自体と同じくらいアクセス可能である必要があります (§7.5.5)。
indexer_bodyは、ステートメント本体 (§15.7.1) または式本体 (§15.6.1) で構成できます。 ステートメント本体では、 accessor_declarationsは "{
" トークンと "}
" トークンで囲まれ、インデクサーのアクセサー (§15.7.3) を宣言します。 アクセサーは、インデクサー要素の読み取りと書き込みに関連付けられている実行可能ステートメントを指定します。
indexer_bodyでは、"=>
" の後に式E
とセミコロンで構成される式本体はステートメント本体の{ get { return E; } }
とまったく同じであるため、get アクセサーの結果が 1 つの式によって与えられる読み取り専用インデクサーの指定にのみ使用できます。
ref_indexer_bodyは、ステートメント本体または式本体で構成される場合があります。 ステートメント本体では、 get_accessor_declaration はインデクサーの get アクセサー (§15.7.3) を宣言します。 アクセサーは、インデクサーの読み取りに関連付けられている実行可能ステートメントを指定します。
ref_indexer_bodyでは、=>
とそれに続くref
で構成される式本体では、variable_reference V
とセミコロンはステートメント本体の{ get { return ref V; } }
とまったく同じです。
注: インデクサー要素にアクセスするための構文は配列要素の構文と同じですが、インデクサー要素は変数として分類されません。 したがって、インデクサー要素を
in
、out
、またはref
引数として渡すことができません。ただし、インデクサーが参照値であるため、参照を返す (§9.7)。 end note
インデクサーの parameter_list は、インデクサーのシグネチャ (§7.6) を定義します。 具体的には、インデクサーのシグネチャは、パラメーターの数と型で構成されます。 パラメーターの要素の型と名前は、インデクサーのシグネチャの一部ではありません。
インデクサーのシグネチャは、同じクラスで宣言されている他のすべてのインデクサーのシグネチャとは異なる必要があります。
インデクサー宣言に extern
修飾子が含まれている場合、インデクサーは 外部インデクサーと言われます。 外部インデクサー宣言では実際の実装が提供されないため、accessor_declarations内の各accessor_bodyはセミコロンにする必要があります。
例: 次の例では、ビット配列内の個々のビットにアクセスするためのインデクサーを実装する
BitArray
クラスを宣言しています。class BitArray { int[] bits; int length; public BitArray(int length) { if (length < 0) { throw new ArgumentException(); } bits = new int[((length - 1) >> 5) + 1]; this.length = length; } public int Length => length; public bool this[int index] { get { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } return (bits[index >> 5] & 1 << index) != 0; } set { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } if (value) { bits[index >> 5] |= 1 << index; } else { bits[index >> 5] &= ~(1 << index); } } } }
BitArray
クラスのインスタンスは、対応するbool[]
よりも実質的に少ないメモリを消費します (前者の各値は、後者の 1 つのbyte
ではなく 1 ビットしか占有しないため)、bool[]
と同じ操作を許可します。次の
CountPrimes
クラスでは、BitArray
と古典的な "シーブ" アルゴリズムを使用して、2 から特定の最大値までの素数を計算します。class CountPrimes { static int Count(int max) { BitArray flags = new BitArray(max + 1); int count = 0; for (int i = 2; i <= max; i++) { if (!flags[i]) { for (int j = i * 2; j <= max; j += i) { flags[j] = true; } count++; } } return count; } static void Main(string[] args) { int max = int.Parse(args[0]); int count = Count(max); Console.WriteLine($"Found {count} primes between 2 and {max}"); } }
BitArray
の要素にアクセスするための構文は、bool[]
の構文とまったく同じであることに注意してください。次の例は、2 つのパラメーターを持つインデクサーを持つ 26×10 グリッド クラスを示しています。 最初のパラメーターは、A ~ Z の範囲で大文字または小文字にする必要があり、2 番目のパラメーターは 0 から 9 の範囲の整数である必要があります。
class Grid { const int NumRows = 26; const int NumCols = 10; int[,] cells = new int[NumRows, NumCols]; public int this[char row, int col] { get { row = Char.ToUpper(row); if (row < 'A' || row > 'Z') { throw new ArgumentOutOfRangeException("row"); } if (col < 0 || col >= NumCols) { throw new ArgumentOutOfRangeException ("col"); } return cells[row - 'A', col]; } set { row = Char.ToUpper(row); if (row < 'A' || row > 'Z') { throw new ArgumentOutOfRangeException ("row"); } if (col < 0 || col >= NumCols) { throw new ArgumentOutOfRangeException ("col"); } cells[row - 'A', col] = value; } } }
end の例
15.9.2 インデクサーとプロパティの違い
インデクサーとプロパティの概念は非常に似ていますが、次の点で異なります。
- プロパティは名前で識別されますが、インデクサーは署名によって識別されます。
- プロパティには、 simple_name (§12.8.4) または member_access (§12.8.7) を介してアクセスします。一方、インデクサー要素には element_access (§12.8.12.3) を介してアクセスします。
- プロパティは静的メンバーにすることができますが、インデクサーは常にインスタンス メンバーです。
- プロパティの get アクセサーはパラメーターのないメソッドに対応しますが、インデクサーの get アクセサーはインデクサーと同じパラメーター リストを持つメソッドに対応します。
- プロパティの set アクセサーは、
value
という名前の 1 つのパラメーターを持つメソッドに対応します。一方、インデクサーのセット アクセサーは、インデクサーと同じパラメーター リストとvalue
という名前の追加パラメーターを持つメソッドに対応します。 - インデクサー アクセサーが、インデクサー パラメーターと同じ名前のローカル変数またはローカル定数を宣言するのはコンパイル時エラーです。
- オーバーライドするプロパティ宣言では、継承されたプロパティは構文
base.P
を使用してアクセスされます。ここで、P
はプロパティ名です。 オーバーライドするインデクサー宣言では、継承されたインデクサーは構文base[E]
を使用してアクセスされます。ここで、E
は式のコンマ区切りのリストです。 - "自動的に実装されるインデクサー" という概念はありません。 セミコロン accessor_bodyを持つ非抽象、非外部インデクサーがあるとエラーになります。
これらの違いを除いて、 §15.7.3、 §15.7.5 および §15.7.6 で定義されているすべての規則は インデクサー アクセサーとプロパティ アクセサーに適用されます。
§15.7.3、§15.7.5 および §15.7.6 を読み取るときに、プロパティ/プロパティをインデクサー/インデクサーに置き換えることも、定義された用語にも適用されます。 具体的には、read-write プロパティ読み取り/書き込みインデクサー、読み取り専用プロパティ読み取り専用のインデクサー になり書き込み専用のインデクサーになります。
15.10 演算子
15.10.1 全般
operator は、クラスのインスタンスに適用できる式演算子の意味を定義するメンバーです。 演算子は、 operator_declarationを使用して宣言されます。
operator_declaration
: attributes? operator_modifier+ operator_declarator operator_body
;
operator_modifier
: 'public'
| 'static'
| 'extern'
| unsafe_modifier // unsafe code support
;
operator_declarator
: unary_operator_declarator
| binary_operator_declarator
| conversion_operator_declarator
;
unary_operator_declarator
: type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
;
logical_negation_operator
: '!'
;
overloadable_unary_operator
: '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
;
binary_operator_declarator
: type 'operator' overloadable_binary_operator
'(' fixed_parameter ',' fixed_parameter ')'
;
overloadable_binary_operator
: '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' fixed_parameter ')'
| 'explicit' 'operator' type '(' fixed_parameter ')'
;
operator_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
注: プレフィックス論理否定 (§12.9.4) と後置 null 許容演算子 (§12.8.9) は、同じ字句トークン (!
) で表されますが、区別されます。 後者はオーバーロード可能な演算子ではありません。 end note
オーバーロード可能な演算子には、単項演算子 (§15.10.2)、二項演算子 (§15.10.3)、および変換演算子 (§15.10.4) の 3 つのカテゴリがあります。
operator_bodyは、セミコロン、ブロック本文 (§15.6.1) または式本体 (§15.6.1) です。 ブロック本体は、 block で構成され、演算子の呼び出し時に実行するステートメントを指定します。 block は、§15.6.11 で説明されている値を返すメソッドの規則に準拠。 式本体は、 =>
の後に式とセミコロンで構成され、演算子が呼び出されたときに実行する 1 つの式を表します。
extern
演算子の場合、operator_bodyは単にセミコロンで構成されます。 他のすべての演算子の場合、 operator_body はブロック本体または式本体です。
次の規則は、すべての演算子宣言に適用されます。
- 演算子宣言には、
public
とstatic
修飾子の両方を含める必要があります。 - 演算子のパラメーターには、
in
以外の修飾子は含まない必要があります。 - 演算子 (§15.10.2、 §15.10.3、 §15.10.4) のシグネチャは、同じクラスで宣言されている他のすべての演算子のシグネチャとは異なります。
- 演算子宣言で参照されるすべての型は、少なくとも演算子自体と同じくらいアクセス可能である必要があります (§7.5.5)。
- 演算子宣言で同じ修飾子が複数回出現するのはエラーです。
各演算子カテゴリは、次のサブクラスで説明されているように、追加の制限を課します。
他のメンバーと同様に、基底クラスで宣言された演算子は派生クラスによって継承されます。 演算子宣言では、演算子が宣言されているクラスまたは構造体が常に演算子のシグネチャに参加する必要があるため、派生クラスで宣言された演算子が基底クラスで宣言された演算子を非表示にすることはできません。 したがって、演算子宣言では、 new
修飾子は必要ないため、許可されません。
単項演算子と二項演算子の詳細については、 §12.4 を参照してください。
変換演算子の詳細については、 §10.5 を参照してください。
15.10.2 単項演算子
次の規則は単項演算子宣言に適用されます。ここで、 T
は、演算子宣言を含むクラスまたは構造体のインスタンス型を表します。
- 単項
+
、-
、!
(論理否定のみ)、または~
演算子は、T
型またはT?
型の 1 つのパラメーターを受け取り、任意の型を返すことができます。 - 単項
++
または--
演算子は、T
型またはT?
型の 1 つのパラメーターを受け取り、同じ型またはそこから派生した型を返す必要があります。 - 単項
true
またはfalse
演算子は、T
型またはT?
型の 1 つのパラメーターを受け取り、型bool
を返す必要があります。
単項演算子のシグネチャは、演算子トークン (+
、 -
、 !
、 ~
、 ++
、 --
、 true
、または false
) と単一パラメーターの型で構成されます。 戻り値の型は、単項演算子のシグネチャの一部ではなく、パラメーターの名前でもありません。
true
とfalse
単項演算子には、ペアワイズ宣言が必要です。 クラスでこれらの演算子の 1 つを宣言し、もう一方を宣言しない場合、コンパイル時エラーが発生します。 true
演算子とfalse
演算子については、§12.24 で詳しく説明します。
例: 次の例は、整数ベクター クラスに対する operator++ の実装とその後の使用方法を示しています。
public class IntVector { public IntVector(int length) {...} public int Length { get { ... } } // Read-only property public int this[int index] { get { ... } set { ... } } // Read-write indexer public static IntVector operator++(IntVector iv) { IntVector temp = new IntVector(iv.Length); for (int i = 0; i < iv.Length; i++) { temp[i] = iv[i] + 1; } return temp; } } class Test { static void Main() { IntVector iv1 = new IntVector(4); // Vector of 4 x 0 IntVector iv2; iv2 = iv1++; // iv2 contains 4 x 0, iv1 contains 4 x 1 iv2 = ++iv1; // iv2 contains 4 x 2, iv1 contains 4 x 2 } }
演算子メソッドは、後置インクリメント演算子とデクリメント演算子 (§12.8.16) とプレフィックスインクリメント演算子とデクリメント演算子 (§12.9.6) と同様に、オペランドに 1 を追加して生成された値を返す方法に注意してください。 C++ とは異なり、このメソッドはオペランドの値を直接変更しないでください。これは、後置インクリメント演算子 (§12.8.16) の標準セマンティクスに違反します。
end の例
15.10.3 二項演算子
次の規則は、二項演算子宣言に適用されます。ここで、 T
は、演算子宣言を含むクラスまたは構造体のインスタンス型を表します。
- 二項非シフト演算子は、2 つのパラメーターを受け取る必要があります。少なくとも 1 つは型
T
またはT?
を持ち、任意の型を返すことができます。 - 二項
<<
演算子または>>
演算子 (§12.11) は、2 つのパラメーターを受け取る必要があります。1 つ目のパラメーターは型T
または T? で、2 番目のパラメーターは型int
またはint?
を持ち、任意の型を返すことができます。
二項演算子のシグネチャは、演算子トークン (+
、 -
、 *
、 /
、 %
、 &
、 |
、 ^
、 <<
、 >>
、 ==
、 !=
、 >
、 <
、 >=
、または <=
) と 2 つのパラメーターの型で構成されます。 戻り値の型とパラメーターの名前は、二項演算子のシグネチャの一部ではありません。
特定の二項演算子には、ペアワイズ宣言が必要です。 ペアのいずれかの演算子の宣言ごとに、ペアの他の演算子の一致する宣言が存在する必要があります。 戻り値の型と対応するパラメーター型の間に ID 変換が存在する場合、2 つの演算子宣言が一致します。 次の演算子には、ペアワイズ宣言が必要です。
- 演算子
==
と演算子!=
- 演算子
>
と演算子<
- 演算子
>=
と演算子<=
15.10.4 変換演算子
変換演算子宣言では、 ユーザー定義の変換 (§10.5) が導入され、事前に定義された暗黙的および明示的な変換が強化されます。
implicit
キーワードを含む変換演算子宣言では、ユーザー定義の暗黙的な変換が導入されます。 暗黙的な変換は、関数メンバーの呼び出し、キャスト式、代入など、さまざまな状況で発生する可能性があります。 これについては、 §10.2 で詳しく説明します。
explicit
キーワードを含む変換演算子宣言では、ユーザー定義の明示的な変換が導入されます。 明示的な変換はキャスト式で行うことができます。詳細については、 §10.3 を参照してください。
変換演算子は、変換演算子のパラメーター型で示されるソース型から、変換演算子の戻り値の型で示されるターゲット型に変換します。
特定のソース型 S
とターゲット型 T
の場合、 S
または T
が null 許容値型の場合は、 S₀
と T₀
が基になる型を参照します。それ以外の場合、 S₀
と T₀
はそれぞれ S
と T
と等しくなります。 クラスまたは構造体は、次のすべてに該当する場合にのみ、ソース型 S
からターゲット型への変換 T
宣言できます。
S₀
とT₀
は異なる型です。S₀
またはT₀
は、演算子宣言を含むクラスまたは構造体のインスタンス型です。S₀
もT₀
もinterface_type。ユーザー定義の変換を除き、
S
からT
への変換、またはT
からS
への変換は存在しません。
これらのルールの目的上、 S
または T
に関連付けられている型パラメーターは、他の型との継承関係のない一意の型と見なされ、それらの型パラメーターに対する制約は無視されます。
例: 次の例を参照してください。
class C<T> {...} class D<T> : C<T> { public static implicit operator C<int>(D<T> value) {...} // Ok public static implicit operator C<string>(D<T> value) {...} // Ok public static implicit operator C<T>(D<T> value) {...} // Error }
最初の 2 つの演算子宣言は、
T
とint
とstring
がそれぞれリレーションシップのない一意の型と見なされるために許可されます。 ただし、C<T>
はD<T>
の基底クラスであるため、3 番目の演算子はエラーです。end の例
2 番目の規則では、変換演算子は、演算子が宣言されているクラスまたは構造体型との間で変換する必要があります。
例: クラスまたは構造体の型
C
で、C
からint
への変換とint
からC
への変換を定義できますが、int
からbool
への変換は定義できません。 end の例
定義済みの変換を直接再定義することはできません。 したがって、変換演算子は、object
とその他のすべての型の間に暗黙的な変換と明示的な変換が既に存在するため、object
との間での変換は許可されません。 同様に、変換元と変換先のどちらの型も、もう一方の基本型にすることはできません。変換は既に存在するためです。 ただし、特定の型引数に対して、定義済みの変換として既に存在する変換を指定するジェネリック型に対して演算子を宣言できます。
例:
struct Convertible<T> { public static implicit operator Convertible<T>(T value) {...} public static explicit operator T(Convertible<T> value) {...} }
型
object
がT
の型引数として指定されている場合、2 番目の演算子は既に存在する変換を宣言します (暗黙的であり、したがって、任意の型から型オブジェクトへの明示的な変換が存在します)。end の例
2 つの型間に事前に定義された変換が存在する場合、それらの型間のユーザー定義変換は無視されます。 具体的には、次のように使用します。
- 定義済みの暗黙的な変換 (§10.2) が型
S
から型T
に存在する場合、S
からT
へのすべてのユーザー定義変換 (暗黙的または明示的) は無視されます。 - 事前に定義された明示的な変換 (§10.3) が型
S
から型T
に存在する場合、S
からT
へのユーザー定義の明示的な変換は無視されます。 その上:S
またはT
がインターフェイス型の場合、S
からT
へのユーザー定義の暗黙的な変換は無視されます。- それ以外の場合、
S
からT
へのユーザー定義の暗黙的な変換は引き続き考慮されます。
object
以外のすべての型について、上記のConvertible<T>
型によって宣言された演算子は、事前に定義された変換と競合しません。
例:
void F(int i, Convertible<int> n) { i = n; // Error i = (int)n; // User-defined explicit conversion n = i; // User-defined implicit conversion n = (Convertible<int>)i; // User-defined implicit conversion }
ただし、型
object
の場合、定義済みの変換では、1 つを含むすべてのケースでユーザー定義の変換が非表示になります。void F(object o, Convertible<object> n) { o = n; // Pre-defined boxing conversion o = (object)n; // Pre-defined boxing conversion n = o; // User-defined implicit conversion n = (Convertible<object>)o; // Pre-defined unboxing conversion }
end の例
ユーザー定義の変換は、 interface_typeから変換することも、interface_typeに変換することもできません。 特に、この制限により、interface_typeに変換するときにユーザー定義の変換が行われなくなり、変換されるobject
が実際に指定されたinterface_typeを実装している場合にのみ、interface_typeへの変換が成功します。
変換演算子のシグネチャは、ソース型とターゲット型で構成されます。 (これは、戻り値の型が署名に参加するメンバーの唯一の形式です)。変換演算子の暗黙的または明示的な分類は、演算子のシグネチャの一部ではありません。 したがって、クラスまたは構造体は、同じソース型とターゲット型を持つ暗黙的な変換演算子と明示的な変換演算子の両方を宣言できません。
注: 一般に、ユーザー定義の暗黙的な変換は、例外をスローし、情報を失わないよう設計する必要があります。 ユーザー定義の変換によって例外が発生する可能性がある場合 (たとえば、ソース引数が範囲外であるため)、または情報の損失 (上位ビットの破棄など) が発生する可能性がある場合は、その変換を明示的な変換として定義する必要があります。 end note
例: 次のコード内
public struct Digit { byte value; public Digit(byte value) { if (value < 0 || value > 9) { throw new ArgumentException(); } this.value = value; } public static implicit operator byte(Digit d) => d.value; public static explicit operator Digit(byte b) => new Digit(b); }
Digit
からbyte
への変換は、例外をスローしたり情報を失ったりしないため暗黙的ですが、byte
からDigit
への変換は明示的です。Digit
はbyte
の可能な値のサブセットのみを表すことができるためです。end の例
15.11 インスタンス コンストラクター
15.11.1 全般
"インスタンス コンストラクター" は、クラスのインスタンスを初期化するために必要なアクションを実装するメンバーです。 インスタンス コンストラクターは、 constructor_declarationを使用して宣言されます。
constructor_declaration
: attributes? constructor_modifier* constructor_declarator constructor_body
;
constructor_modifier
: 'public'
| 'protected'
| 'internal'
| 'private'
| 'extern'
| unsafe_modifier // unsafe code support
;
constructor_declarator
: identifier '(' parameter_list? ')' constructor_initializer?
;
constructor_initializer
: ':' 'base' '(' argument_list? ')'
| ':' 'this' '(' argument_list? ')'
;
constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
constructor_declarationには、一連のattributes (§22)、許可されている種類の宣言されたアクセシビリティ (§15.3.6)、およびextern
(§15.6.8) 修飾子を含めることができます。 コンストラクター宣言は、同じ修飾子を複数回含められません。
constructor_declaratorのidentifierは、インスタンス コンストラクターが宣言されているクラスに名前を付ける必要があります。 他の名前が指定されている場合は、コンパイル時エラーが発生します。
インスタンス コンストラクターの省略可能な parameter_list は、メソッドの parameter_list と同じ規則に従います (§15.6)。 パラメーターの this
修飾子は拡張メソッド (§15.6.10 にのみ適用されるため、コンストラクターの parameter_list のパラメーターには、 this
修飾子を含む必要はありません。 パラメーター リストは、インスタンス コンストラクターのシグネチャ (§7.6) を定義し、オーバーロード解決 (§12.6.4) が呼び出しで特定のインスタンス コンストラクターを選択するプロセスを制御します。
インスタンス コンストラクターの parameter_list で参照される各型は、少なくともコンストラクター自体と同じくらいアクセス可能である必要があります (§7.5.5)。
省略可能な constructor_initializer は、このインスタンス コンストラクターの constructor_body で指定されたステートメントを実行する前に呼び出す別のインスタンス コンストラクターを指定します。 これについては、 §15.11.2 で詳しく説明します。
コンストラクター宣言に extern
修飾子が含まれている場合、コンストラクターは 外部コンストラクターと呼。 外部コンストラクター宣言は実際の実装を提供しないため、その constructor_body はセミコロンで構成されます。 その他のすべてのコンストラクターの場合、 constructor_body はどちらかで構成されます。
- block。クラスの新しいインスタンスを初期化するステートメントを指定します。
- 式本体。
=>
の後に 式 セミコロンが続き、クラスの新しいインスタンスを初期化するための単一の式を表します。
blockまたは式本体であるconstructor_bodyは、void
戻り値の型 (§15.6.11) を持つインスタンス メソッドのブロックに正確に対応します。
インスタンス コンストラクターは継承されません。 したがって、クラスには、クラスで実際に宣言されたインスタンス コンストラクター以外のインスタンス コンストラクターがありません。ただし、クラスにインスタンス コンストラクター宣言が含まれている場合は、既定のインスタンス コンストラクターが自動的に指定されます (§15.11.5)。
インスタンス コンストラクターは、 object_creation_expression (§12.8.17.2) と constructor_initializerによって呼び出されます。
15.11.2 コンストラクター初期化子
すべてのインスタンス コンストラクター (クラス object
を除く) には、 constructor_bodyの直前に別のインスタンス コンストラクターの呼び出しが暗黙的に含まれます。 暗黙的に呼び出すコンストラクターは、 constructor_initializerによって決定されます。
- フォーム
base(
argument_list)
のインスタンス コンストラクター初期化子 ( argument_list は省略可能) により、ダイレクト 基底クラスのインスタンス コンストラクターが呼び出されます。 このコンストラクターは、 argument_list と §12.6.4 のオーバーロード解決規則を使用して選択されます。 候補インスタンス コンストラクターのセットは、直接基底クラスのすべてのアクセス可能なインスタンス コンストラクターで構成されます。 このセットが空の場合、または 1 つの最適なインスタンス コンストラクターを識別できない場合は、コンパイル時エラーが発生します。 - フォーム
this(
argument_list)
のインスタンス コンストラクター初期化子 ( argument_list は省略可能) は、同じクラスから別のインスタンス コンストラクターを呼び出します。 コンストラクターは、 argument_list と §12.6.4 のオーバーロード解決規則を使用して選択されます。 候補インスタンス コンストラクターのセットは、クラス自体で宣言されているすべてのインスタンス コンストラクターで構成されます。 該当するインスタンス コンストラクターの結果のセットが空の場合、または 1 つの最適なインスタンス コンストラクターを識別できない場合は、コンパイル時エラーが発生します。 インスタンス コンストラクター宣言が 1 つ以上のコンストラクター初期化子のチェーンを介して自身を呼び出すと、コンパイル時エラーが発生します。
インスタンス コンストラクターにコンストラクター初期化子がない場合は、 base()
形式のコンストラクター初期化子が暗黙的に提供されます。
注: したがって、フォームのインスタンス コンストラクター宣言
C(...) {...}
は、次とまったく同じです。
C(...) : base() {...}
end note
インスタンス コンストラクター宣言の parameter_list によって指定されるパラメーターのスコープには、その宣言のコンストラクター初期化子が含まれます。 したがって、コンストラクター初期化子は、コンストラクターのパラメーターにアクセスできます。
例:
class A { public A(int x, int y) {} } class B: A { public B(int x, int y) : base(x + y, x - y) {} }
end の例
インスタンス コンストラクター初期化子は、作成されるインスタンスにアクセスできません。 したがって、コンストラクター初期化子の引数式でこれを参照するのはコンパイル時エラーです。これは、引数式が simple_nameを介してインスタンス メンバーを参照するためのコンパイル時エラーであるためです。
15.11.3 インスタンス変数初期化子
インスタンス コンストラクターにコンストラクター初期化子がない場合、またはフォーム base(...)
のコンストラクター初期化子がある場合、そのコンストラクターは、クラスで宣言されたインスタンス フィールドの variable_initializerで指定された初期化を暗黙的に実行します。 これは、コンストラクターへのエントリの直後、および直接基底クラス コンストラクターの暗黙的な呼び出しの前に実行される一連の割り当てに対応します。 変数初期化子は、クラス宣言 (§15.5.6) に表示されるテキストの順序で実行されます。
15.11.4 コンストラクターの実行
変数初期化子は代入ステートメントに変換され、これらの代入ステートメントは基底クラス インスタンス コンストラクターの呼び出し 前 実行されます。 この順序により、そのインスタンスにアクセスできる any ステートメントが実行される前に、すべてのインスタンス フィールドが変数初期化子によって初期化されます。
例: 以下を指定します。
class A { public A() { PrintFields(); } public virtual void PrintFields() {} } class B: A { int x = 1; int y; public B() { y = -1; } public override void PrintFields() => Console.WriteLine($"x = {x}, y = {y}"); }
新しい
B()
を使用してB
のインスタンスを作成すると、次の出力が生成されます。x = 1, y = 0
基底クラス インスタンス コンストラクターが呼び出される前に変数初期化子が実行されるため、
x
の値は 1 です。 ただし、y
の値は 0 (int
の既定値) です。これは、基底クラスコンストラクターが戻るまで、y
への割り当てが実行されないためです。 インスタンス変数初期化子とコンストラクター初期化子は、 constructor_bodyの前に自動的に挿入されるステートメントと考えると便利です。 例を示します。class A { int x = 1, y = -1, count; public A() { count = 0; } public A(int n) { count = n; } } class B : A { double sqrt2 = Math.Sqrt(2.0); ArrayList items = new ArrayList(100); int max; public B(): this(100) { items.Add("default"); } public B(int n) : base(n - 1) { max = n; } }
には、複数の変数初期化子が含まれています。また、両方の形式 (
base
とthis
) のコンストラクター初期化子も含まれています。 この例は、次に示すコードに対応しています。各コメントは自動的に挿入されたステートメントを示します (自動的に挿入されたコンストラクター呼び出しに使用される構文は有効ではありませんが、メカニズムを示すためだけに機能します)。class A { int x, y, count; public A() { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = 0; } public A(int n) { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = n; } } class B : A { double sqrt2; ArrayList items; int max; public B() : this(100) { B(100); // Invoke B(int) constructor items.Add("default"); } public B(int n) : base(n - 1) { sqrt2 = Math.Sqrt(2.0); // Variable initializer items = new ArrayList(100); // Variable initializer A(n - 1); // Invoke A(int) constructor max = n; } }
end の例
15.11.5 既定のコンストラクター
クラスにインスタンス コンストラクター宣言が含まれている場合、既定のインスタンス コンストラクターが自動的に提供されます。 その既定のコンストラクターは、フォーム base()
のコンストラクター初期化子があるかのように、ダイレクト 基底クラスのコンストラクターを呼び出すだけです。 クラスが抽象クラスの場合、既定のコンストラクターの宣言されたアクセシビリティが保護されます。 それ以外の場合、既定のコンストラクターに対して宣言されたアクセシビリティはパブリックです。
注: したがって、既定のコンストラクターは常に形式です。
protected C(): base() {}
または
public C(): base() {}
ここで、
C
はクラスの名前です。end note
オーバーロードの解決で基底クラスコンストラクター初期化子の一意の最適な候補を特定できない場合は、コンパイル時エラーが発生します。
例: 次のコード内
class Message { object sender; string text; }
クラスにインスタンス コンストラクター宣言がないため、既定のコンストラクターが提供されます。 したがって、この例は
class Message { object sender; string text; public Message() : base() {} }
end の例
15.12 静的コンストラクター
静的コンストラクターは、閉じたクラスを初期化するために必要なアクションを実装するメンバーです。 静的コンストラクターは、 static_constructor_declarationを使用して宣言されます。
static_constructor_declaration
: attributes? static_constructor_modifiers identifier '(' ')'
static_constructor_body
;
static_constructor_modifiers
: 'static'
| 'static' 'extern' unsafe_modifier?
| 'static' unsafe_modifier 'extern'?
| 'extern' 'static' unsafe_modifier?
| 'extern' unsafe_modifier 'static'
| unsafe_modifier 'static' 'extern'?
| unsafe_modifier 'extern' 'static'
;
static_constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
static_constructor_declarationには、attributes (§22) とextern
修飾子 (§15.6.8) を含めることができます。
static_constructor_declarationのidentifierは、静的コンストラクターが宣言されているクラスに名前を付ける必要があります。 他の名前が指定されている場合は、コンパイル時エラーが発生します。
静的コンストラクター宣言に extern
修飾子が含まれている場合、静的コンストラクターは 外部の静的コンストラクターと呼。 外部静的コンストラクター宣言は実際の実装を提供しないため、その static_constructor_body はセミコロンで構成されます。 その他のすべての静的コンストラクター宣言の場合、 static_constructor_body はどちらかで構成されます。
- block。クラスを初期化するために実行するステートメントを指定します。
- 式本体。
=>
の後に expression セミコロンが続き、クラスを初期化するために実行する 1 つの式を表します。
blockまたは式本体であるstatic_constructor_bodyは、void
戻り値の型 (§15.6.11) を持つ静的メソッドのmethod_bodyに正確に対応します。
静的コンストラクターは継承されず、直接呼び出すことはできません。
閉じたクラスの静的コンストラクターは、特定のアプリケーション ドメインで最大 1 回実行されます。 静的コンストラクターの実行は、アプリケーション ドメイン内で発生する次のイベントの最初のイベントによってトリガーされます。
- クラスのインスタンスが作成されます。
- クラスの静的メンバーのいずれかが参照されます。
実行を開始する Main
メソッド (§7.1) がクラスに含まれている場合、そのクラスの静的コンストラクターは、 Main
メソッドが呼び出される前に実行されます。
新しい閉じたクラス型を初期化するには、まず、その特定の閉じた型の静的フィールドの新しいセット (§15.5.2) が作成されます。 各静的フィールドは既定値 (§15.5.5) に初期化されます。 次に、これらの静的フィールドに対して静的フィールド初期化子 (§15.5.6.2) が実行されます。 最後に、静的コンストラクターが実行されます。
例: 例
class Test { static void Main() { A.F(); B.F(); } } class A { static A() { Console.WriteLine("Init A"); } public static void F() { Console.WriteLine("A.F"); } } class B { static B() { Console.WriteLine("Init B"); } public static void F() { Console.WriteLine("B.F"); } }
出力を生成する必要があります。
Init A A.F Init B B.F
A
の静的コンストラクターの実行はA.F
の呼び出しによってトリガーされ、B
の静的コンストラクターの実行はB.F
の呼び出しによってトリガーされるためです。end の例
変数初期化子を持つ静的フィールドを既定値の状態で観察できるようにする循環依存関係を構築できます。
例: 例
class A { public static int X; static A() { X = B.Y + 1; } } class B { public static int Y = A.X + 1; static B() {} static void Main() { Console.WriteLine($"X = {A.X}, Y = {B.Y}"); } }
この例では、次のように出力されます。
X = 1, Y = 2
Main
メソッドを実行するために、システムはクラスB
の静的コンストラクターの前に、最初にB.Y
の初期化子を実行します。Y
's initializer により、A
のstatic
コンストラクターが実行されます。これは、A.X
の値が参照されるためです。A
の静的コンストラクターは、X
の値の計算に進み、その際に既定値のY
(ゼロ) をフェッチします。A.X
したがって、1 に初期化されます。A
の静的フィールド初期化子と静的コンストラクターを実行するプロセスが完了し、Y
の初期値の計算に戻り、その結果は 2 になります。end の例
静的コンストラクターは、閉じた構築されたクラス型ごとに 1 回だけ実行されるため、制約 (§15.2.5) を使用してコンパイル時にチェックできない型パラメーターに対して実行時チェックを適用すると便利です。
例: 次の型は、静的コンストラクターを使用して、型引数が列挙型であることを強制します。
class Gen<T> where T : struct { static Gen() { if (!typeof(T).IsEnum) { throw new ArgumentException("T must be an enum"); } } }
end の例
15.13 ファイナライザー
注: この仕様の以前のバージョンでは、現在は "ファイナライザー" と呼ばれるものが "デストラクター" と呼ばれていました。 経験上、"デストラクター" という用語は混乱を引き起こし、多くの場合、特に C++ を知っているプログラマにとって、不適切な期待を引き起こしました。 C++ ではデストラクターは確定的な方法で呼び出されますが、C# ではファイナライザーは呼び出されません。 C# から確定的な動作を取得するには、
Dispose
を使用する必要があります。 end note
"ファイナライザー" は、クラスのインスタンスを最終処理するために必要なアクションを実装するメンバーです。 ファイナライザーは、 finalizer_declarationを使用して宣言されます。
finalizer_declaration
: attributes? '~' identifier '(' ')' finalizer_body
| attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
finalizer_body
| attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
finalizer_body
;
finalizer_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。
finalizer_declarationには、attributes (§22) のセットを含めることができます。
finalizer_declaratorのidentifierは、ファイナライザーが宣言されているクラスに名前を付ける必要があります。 他の名前が指定されている場合は、コンパイル時エラーが発生します。
ファイナライザー宣言に extern
修飾子が含まれている場合、ファイナライザーは 外部ファイナライザーと見なされます。 外部ファイナライザー宣言は実際の実装を提供しないため、その finalizer_body はセミコロンで構成されます。 その他すべてのファイナライザーでは、 finalizer_body は次の要素で構成されます。
- block。クラスのインスタンスを最終処理するために実行するステートメントを指定します。
- または式本体。
=>
の後に expression セミコロンが続き、クラスのインスタンスを最終処理するために実行する 1 つの式を表します。
blockまたは式本体であるfinalizer_bodyは、void
戻り値の型 (§15.6.11) を持つインスタンス メソッドのmethod_bodyに正確に対応します。
ファイナライザーは継承されません。 したがって、クラスには、そのクラスで宣言できるファイナライザー以外のファイナライザーはありません。
注: ファイナライザーはパラメーターを持たないようにする必要があるため、オーバーロードできないため、クラスは最大で 1 つのファイナライザーを持つことができます。 end note
ファイナライザーは自動的に呼び出され、明示的に呼び出すことはできません。 どのコードでもそのインスタンスを使用できなくなった場合、インスタンスは最終処理の対象になります。 インスタンスのファイナライザーの実行は、インスタンスが最終処理の対象になるといつでも発生する可能性があります (§7.9)。 インスタンスが最終処理されると、そのインスタンスの継承チェーン内のファイナライザーが、ほとんどの派生から最も派生が少ない順に呼び出されます。 ファイナライザーは、任意のスレッドで実行できます。 ファイナライザーを実行するタイミングと方法を制御する規則の詳細については、 §7.9 を参照してください。
例: 例の出力
class A { ~A() { Console.WriteLine("A's finalizer"); } } class B : A { ~B() { Console.WriteLine("B's finalizer"); } } class Test { static void Main() { B b = new B(); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
is
B's finalizer A's finalizer
継承チェーン内のファイナライザーは、ほとんどの派生から最も派生が少ないため、順番に呼び出されるためです。
end の例
ファイナライザーは、System.Object
でFinalize
仮想メソッドをオーバーライドすることによって実装されます。 C# プログラムでは、このメソッドをオーバーライドしたり、メソッドを直接呼び出したり (またはオーバーライドしたり)することはできません。
例: たとえば、プログラム
class A { override protected void Finalize() {} // Error public void F() { this.Finalize(); // Error } }
には 2 つのエラーが含まれています。
end の例
コンパイラは、このメソッドと同様に動作し、オーバーライドは存在しません。
例: したがって、このプログラム:
class A { void Finalize() {} // Permitted }
は有効であり、表示されているメソッドは
System.Object
のFinalize
メソッドを非表示にします。end の例
ファイナライザーから例外がスローされたときの動作については、 §21.4 を参照してください。
15.14 反復子
15.14.1 全般
反復子ブロック (§13.3) を使用して実装される関数メンバー (§12.6) は、iterator と呼ばれます。
反復子ブロックは、対応する関数メンバーの戻り値の型が列挙子インターフェイス (§15.14.2) または列挙可能なインターフェイス (§15.14.3 のいずれかである限り、関数メンバーの本体として使用できます。 これは、 method_body、 operator_body 、または accessor_bodyとして発生する可能性があります。一方、イベント、インスタンス コンストラクター、静的コンストラクター、ファイナライザーは反復子として実装されません。
関数メンバーが反復子ブロックを使用して実装されている場合、関数メンバーのパラメーター リストで、 in
、 out
、または ref
パラメーター、または ref struct
型のパラメーターを指定するのはコンパイル時エラーです。
15.14.2 列挙子インターフェイス
インスタンス化インターフェイスは、非ジェネリック インターフェイス System.Collections.IEnumerator
であり、ジェネリック インターフェイスSystem.Collections.Generic.IEnumerator<T>
のすべてのインスタンス化です。 簡潔にするために、このサブクラスとその兄弟では、これらのインターフェイスはそれぞれ IEnumerator
および IEnumerator<T>
として参照されます。
15.14.3 列挙可能なインターフェイス
可能なインターフェイスは、非ジェネリック インターフェイス System.Collections.IEnumerable
であり、ジェネリック インターフェイスSystem.Collections.Generic.IEnumerable<T>
のすべてのインスタンス化です。 簡潔にするために、このサブクラスとその兄弟では、これらのインターフェイスはそれぞれ IEnumerable
および IEnumerable<T>
として参照されます。
15.14.4 収量の種類
反復子は、同じ型の値のシーケンスを生成します。 この型は、反復子の 型 と呼ばれます。
IEnumerator
またはIEnumerable
を返す反復子の yield 型がobject
。IEnumerator<T>
またはIEnumerable<T>
を返す反復子の yield 型がT
。
15.14.5 列挙子オブジェクト
15.14.5.1 全般
列挙子インターフェイス型を返す関数メンバーが反復子ブロックを使用して実装されている場合、関数メンバーを呼び出しても、反復子ブロック内のコードは直ちに実行されません。 代わりに、 enumerator オブジェクト が作成されて返されます。 このオブジェクトは反復子ブロックで指定されたコードをカプセル化し、反復子ブロック内のコードの実行は、列挙子オブジェクトの MoveNext
メソッドが呼び出されたときに発生します。 列挙子オブジェクトには、次の特性があります。
IEnumerator
とIEnumerator<T>
を実装します。ここで、T
は反復子の yield 型です。- このクラスは、
System.IDisposable
を実装します。 - 関数メンバーに渡される引数値 (存在する場合) とインスタンス値のコピーを使用して初期化されます。
- これには、fore、running、suspended、および の 4 つの潜在的な状態があり、最初は fore 状態です。
列挙子オブジェクトは通常、反復子ブロックにコードをカプセル化し、列挙子インターフェイスを実装するコンパイラによって生成される列挙子クラスのインスタンスですが、他の実装メソッドも可能です。 列挙子クラスがコンパイラによって生成された場合、そのクラスは、関数メンバーを含むクラス内で直接または間接的に入れ子になり、プライベート アクセシビリティを持ち、コンパイラ用に予約された名前を持ちます (§6.4.3)。
列挙子オブジェクトは、上記で指定したインターフェイスよりも多くのインターフェイスを実装できます。
次のサブクラウスでは、列挙子オブジェクトによって提供されるIEnumerator
およびIEnumerator<T>
インターフェイス実装のMoveNext
、Current
、およびDispose
メンバーの必要な動作について説明します。
列挙子オブジェクトは、 IEnumerator.Reset
メソッドをサポートしていません。 このメソッドを呼び出すと、 System.NotSupportedException
がスローされます。
15.14.5.2 MoveNext メソッド
列挙子オブジェクトの MoveNext
メソッドは、反復子ブロックのコードをカプセル化します。 MoveNext
メソッドを呼び出すと、反復子ブロック内のコードが実行され、必要に応じて列挙子オブジェクトのCurrent
プロパティが設定されます。 MoveNext
によって実行される正確なアクションは、MoveNext
が呼び出されたときの列挙子オブジェクトの状態によって異なります。
- 列挙子オブジェクトの状態が 前の場合、
MoveNext
を呼び出します。- 状態を running に変更します。
- 列挙子オブジェクトの初期化時に保存された引数値とインスタンス値に対して、反復子ブロックのパラメーター (
this
を含む) を初期化します。 - 次に示すように、最初から実行が中断されるまで反復子ブロックを実行します。
- 列挙子オブジェクトの状態が 実行場合、
MoveNext
を呼び出した結果は指定されていません。 - 列挙子オブジェクトの状態が suspended 場合は、MoveNext を呼び出します。
- 状態を running に変更します。
- 反復子ブロックの実行が最後に中断されたときに保存された値に、すべてのローカル変数とパラメーター (
this
を含む) の値を復元します。注: これらの変数によって参照されるすべてのオブジェクトの内容は、
MoveNext
の前回の呼び出し以降に変更されている可能性があります。 end note - 実行の中断の原因となった yield return ステートメントの直後に反復子ブロックの実行を再開し、実行が中断されるまで続行します (後述)。
- 列挙子オブジェクトの状態が 後場合、
MoveNext
を呼び出すと false が返されます。
MoveNext
反復子ブロックを実行すると、yield return
ステートメント、yield break
ステートメント、反復子ブロックの末尾、および反復子ブロックからスローされて伝達される例外の 4 つの方法で実行を中断できます。
yield return
ステートメントが検出されたとき (§9.4.4.20):- ステートメントで指定された式が評価され、暗黙的に yield 型に変換され、列挙子オブジェクトの
Current
プロパティに割り当てられます。 - 反復子本体の実行が中断されます。 この
yield return
ステートメントの場所と同様に、すべてのローカル変数とパラメーター (this
を含む) の値が保存されます。yield return
ステートメントが 1 つ以上のtry
ブロック内にある場合、関連付けられた finally ブロックは実行されません現時点で実行されます。 - 列挙子オブジェクトの状態が suspended に変更されます。
MoveNext
メソッドは呼び出し元にtrue
を返し、イテレーションが次の値に正常に進んだことを示します。
- ステートメントで指定された式が評価され、暗黙的に yield 型に変換され、列挙子オブジェクトの
yield break
ステートメントが検出されたとき (§9.4.4.20):yield break
ステートメントが 1 つ以上のtry
ブロック内にある場合は、関連付けられているfinally
ブロックが実行されます。- 列挙子オブジェクトの状態は、 以降に変更されます。
MoveNext
メソッドは、反復処理が完了したことを示すfalse
を呼び出し元に返します。
- 反復子本体の末尾が検出された場合:
- 列挙子オブジェクトの状態は、 以降に変更されます。
MoveNext
メソッドは、反復処理が完了したことを示すfalse
を呼び出し元に返します。
- 例外がスローされ、反復子ブロックから伝達された場合:
- 反復子本体の適切な
finally
ブロックは、例外伝達によって実行されます。 - 列挙子オブジェクトの状態は、 以降に変更されます。
- 例外の伝達は、
MoveNext
メソッドの呼び出し元に引き続き適用されます。
- 反復子本体の適切な
15.14.5.3 Current プロパティ
列挙子オブジェクトの Current
プロパティは、反復子ブロック内の yield return
ステートメントの影響を受けます。
列挙子オブジェクトが suspended 状態の場合、 Current
の値は、 MoveNext
の前の呼び出しによって設定された値です。 列挙子オブジェクトが fore、 running、または 以降 状態にある場合、 Current
にアクセスした結果は指定されません。
object
以外の yield 型を持つ反復子の場合、列挙子オブジェクトのIEnumerable
実装を介してCurrent
にアクセスした結果は、列挙子オブジェクトのIEnumerator<T>
実装を介してCurrent
にアクセスし、結果をobject
にキャストすることに対応します。
15.14.5.4 Dispose メソッド
Dispose
メソッドは、列挙子オブジェクトを after 状態にすることで反復処理をクリーンアップするために使用されます。
- 列挙子オブジェクトの状態が前の場合
Dispose
呼び出すと、状態が 以降に変更されます。 - 列挙子オブジェクトの状態が 実行場合、
Dispose
を呼び出した結果は指定されていません。 - 列挙子オブジェクトの状態が場合は
Dispose
を呼び出します。- 状態を running に変更します。
- 最後に実行された
yield return
ステートメントがyield break
ステートメントであるかのように、finally ブロックを実行します。 これにより、例外がスローされ、反復子本体から伝達される場合、列挙子オブジェクトの状態は 以降に設定され 例外はDispose
メソッドの呼び出し元に伝達されます。 - 状態を 後に変更します。
- 列挙子オブジェクトの状態が 後場合、
Dispose
を呼び出しても影響はありません。
15.14.6 列挙可能なオブジェクト
15.14.6.1 全般
列挙可能なインターフェイス型を返す関数メンバーが反復子ブロックを使用して実装されている場合、関数メンバーを呼び出しても、反復子ブロック内のコードは直ちに実行されません。 代わりに、 実行可能なオブジェクト が作成され、返されます。 列挙可能なオブジェクトの GetEnumerator
メソッドは、反復子ブロックで指定されたコードをカプセル化する列挙子オブジェクトを返します。反復子ブロック内のコードの実行は、列挙子オブジェクトの MoveNext
メソッドが呼び出されたときに発生します。 列挙可能なオブジェクトには、次の特性があります。
IEnumerable
とIEnumerable<T>
を実装します。ここで、T
は反復子の yield 型です。- 関数メンバーに渡される引数値 (存在する場合) とインスタンス値のコピーを使用して初期化されます。
列挙可能なオブジェクトは、通常、反復子ブロックにコードをカプセル化し、列挙可能なインターフェイスを実装するコンパイラによって生成された列挙可能なクラスのインスタンスですが、他の実装メソッドも可能です。 列挙可能なクラスがコンパイラによって生成された場合、そのクラスは、関数メンバーを含むクラス内で直接または間接的に入れ子になり、プライベート アクセシビリティを持ち、コンパイラ用に予約された名前を持ちます (§6.4.3)。
列挙可能なオブジェクトは、上記で指定したインターフェイスよりも多くのインターフェイスを実装できます。
注: たとえば、列挙可能なオブジェクトは、
IEnumerator
とIEnumerator<T>
を実装して、列挙可能オブジェクトと列挙子の両方として機能するようにすることもできます。 通常、このような実装では、GetEnumerator
の最初の呼び出しから (割り当てを保存するために) 独自のインスタンスが返されます。GetEnumerator
の後続の呼び出しがある場合は、通常は同じクラスの新しいクラス インスタンスが返されるため、異なる列挙子インスタンスへの呼び出しは互いに影響を与えなくなります。 前の列挙子がシーケンスの末尾を越えて既に列挙されている場合でも、同じインスタンスを返すことはできません。これは、今後、使い果たされた列挙子に対するすべての呼び出しで例外をスローする必要があるためです。 end note
15.14.6.2 GetEnumerator メソッド
列挙可能なオブジェクトは、IEnumerable
インターフェイスとIEnumerable<T>
インターフェイスのGetEnumerator
メソッドの実装を提供します。 2 つの GetEnumerator
メソッドは、使用可能な列挙子オブジェクトを取得して返す共通の実装を共有します。 列挙子オブジェクトは、列挙可能なオブジェクトの初期化時に保存された引数値とインスタンス値で初期化されますが、それ以外の場合は、 §15.14.5 で説明されているように列挙子オブジェクトが機能します。
15.15 非同期関数
15.15.1 全般
async
修飾子を持つメソッド (§15.6) または匿名関数 (§12.19) は、async 関数と呼ばれます。 一般に、 async という用語は、 async
修飾子を持つ任意の種類の関数を記述するために使用されます。
非同期関数のパラメーター リストで、任意の in
、 out
、または ref
パラメーター、または ref struct
型のパラメーターを指定するのはコンパイル時エラーです。
非同期メソッドの return_type は、 void
または task 型。 結果値を生成する非同期メソッドの場合、タスクの種類はジェネリックである必要があります。 結果値を生成しない非同期メソッドの場合、タスクの種類はジェネリックではありません。 このような型は、本明細書では、それぞれ «TaskType»<T>
および «TaskType»
と呼ばれる。 標準ライブラリの種類System.Threading.Tasks.Task
とSystem.Threading.Tasks.Task<TResult>
から構築される型は、タスクの種類だけでなく、属性System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
を介してtask ビルダー型に関連付けられているクラス、構造体、またはインターフェイス型です。 このような型は、この仕様では «TaskBuilderType»<T>
および «TaskBuilderType»
と呼ばれます。 タスク型は、最大で 1 つの型パラメーターを持つことができず、ジェネリック型で入れ子にすることはできません。
タスク型を返す非同期メソッドは、 task-returning と呼ばれる。
タスクの種類は正確な定義によって異なりますが、言語の観点からは、タスクの種類は状態 incomplete、 ucceeded または faulted のいずれかになります。 既定 タスクは、関連する例外を記録します。 succeeded «TaskType»<T>
はT
型の結果を記録します。 タスク型は待機可能であるため、タスクは await 式のオペランドにすることができます (§12.9.8)。
例: タスクの種類
MyTask<T>
は、タスク ビルダーの種類MyTaskMethodBuilder<T>
に関連付け、awaiter 型Awaiter<T>
。using System.Runtime.CompilerServices; [AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))] class MyTask<T> { public Awaiter<T> GetAwaiter() { ... } } class Awaiter<T> : INotifyCompletion { public void OnCompleted(Action completion) { ... } public bool IsCompleted { get; } public T GetResult() { ... } }
end の例
タスク ビルダー型は、特定のタスクの種類 (§15.15.2) に対応するクラスまたは構造体型です。 タスク ビルダーの種類は、対応するタスクの種類の宣言されたアクセシビリティと完全に一致する必要があります。
注: タスク型が
internal
宣言されている場合は、対応するビルダー型もinternal
宣言し、同じアセンブリで定義する必要があります。 タスクの種類が別の型内に入れ子になっている場合は、タスク の浮力型も同じ型で入れ子にする必要があります。 end note
非同期関数には、本体の await 式 (§12.9.8) を使用して評価を中断する機能があります。 後で、中断中の await 式の時点で、 resumption デリゲートを使用して評価を再開できます。 再開デリゲートは System.Action
型であり、呼び出されると、非同期関数呼び出しの評価は中断された await 式から再開されます。 非同期関数呼び出しの 現在の呼び出し元 は、関数呼び出しが中断されていない場合は元の呼び出し元、それ以外の場合は再開デリゲートの最新の呼び出し元です。
15.15.2 タスクタイプビルダーパターン
タスク ビルダー型には、最大で 1 つの型パラメーターを指定でき、ジェネリック型で入れ子にすることはできません。 タスク ビルダー型には、宣言されたpublic
アクセシビリティを持つ次のメンバー (非ジェネリック タスク ビルダー型の場合、SetResult
にはパラメーターがありません) が含まれている必要があります。
class «TaskBuilderType»<T>
{
public static «TaskBuilderType»<T> Create();
public void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine;
public void SetStateMachine(IAsyncStateMachine stateMachine);
public void SetException(Exception exception);
public void SetResult(T result);
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine;
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine;
public «TaskType»<T> Task { get; }
}
コンパイラは、«TaskBuilderType» を使用して非同期関数の評価を中断および再開するセマンティクスを実装するコードを生成します。 コンパイラは、次のように «TaskBuilderType» を使用します。
«TaskBuilderType».Create()
は、このリストにbuilder
という名前の «TaskBuilderType» のインスタンスを作成するために呼び出されます。builder.Start(ref stateMachine)
は、ビルダーをコンパイラによって生成されたステート マシン インスタンス (stateMachine
) に関連付けるために呼び出されます。- ビルダーは、
Start()
で、またはステート マシンを進めるためにStart()
が返された後に、stateMachine.MoveNext()
を呼び出す必要があります。
- ビルダーは、
Start()
が戻った後、async
メソッドは、非同期メソッドからタスクが返されるbuilder.Task
を呼び出します。stateMachine.MoveNext()
を呼び出すたびにステート マシンが進みます。- ステート マシンが正常に完了すると、メソッドの戻り値がある場合は、
builder.SetResult()
が呼び出されます。 - それ以外の場合、ステート マシンで例外
e
がスローされると、builder.SetException(e)
が呼び出されます。 - ステート マシンが
await expr
式に達すると、expr.GetAwaiter()
が呼び出されます。 - awaiter が
ICriticalNotifyCompletion
を実装し、IsCompleted
が false の場合、ステート マシンはbuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
を呼び出します。AwaitUnsafeOnCompleted()
は、awaiter が完了したときにstateMachine.MoveNext()
を呼び出すAction
でawaiter.UnsafeOnCompleted(action)
を呼び出す必要があります。
- それ以外の場合、ステート マシンは
builder.AwaitOnCompleted(ref awaiter, ref stateMachine)
を呼び出します。AwaitOnCompleted()
は、awaiter が完了したときにstateMachine.MoveNext()
を呼び出すAction
でawaiter.OnCompleted(action)
を呼び出す必要があります。
SetStateMachine(IAsyncStateMachine)
は、特にステート マシンが値型として実装されている場合に、ステート マシン インスタンスに関連付けられているビルダーのインスタンスを識別するために、コンパイラによって生成されたIAsyncStateMachine
実装によって呼び出される場合があります。- ビルダーが
stateMachine.SetStateMachine(stateMachine)
を呼び出すと、stateMachine
は関連付けられている builder インスタンスでbuilder.SetStateMachine(stateMachine)
を呼び出stateMachine
。
- ビルダーが
注:
SetResult(T result)
と«TaskType»<T> Task { get; }
の両方で、パラメーターと引数はそれぞれ、T
に変換できる ID である必要があります。 これにより、タスク型ビルダーはタプルなどの型をサポートできます。同じではない 2 つの型は ID 変換可能です。 end note
15.15.3 タスクを返す非同期関数の評価
タスクを返す非同期関数を呼び出すと、返されたタスクの種類のインスタンスが生成されます。 これは、非同期関数の 戻りタスク と呼ばれます。 タスクは、最初は incomplete 状態です。
非同期関数本体は、中断 (await 式に到達) するか終了するまで評価され、その時点で制御が戻りタスクと共に呼び出し元に返されます。
非同期関数の本体が終了すると、戻りタスクは不完全な状態から移動されます。
- return ステートメントまたは本文の末尾に到達した結果として関数本体が終了した場合、結果値は戻りタスクに記録され、 ucceeded 状態になります。
- キャッチされていない
OperationCanceledException
が原因で関数本体が終了した場合、例外は戻りタスクに記録され、 anceled 状態になります。 - 関数本体が他のキャッチされていない例外 (§13.10.6) の結果として終了した場合、例外は戻りタスクに記録され、状態になります。
15.15.4 void を返す非同期関数の評価
非同期関数の戻り値の型が void
場合、評価は上記とは異なります。タスクが返されないため、関数は代わりに、現在のスレッドの 同期コンテキストに完了と例外を伝えます。 同期コンテキストの正確な定義は実装に依存しますが、現在のスレッドが実行されている "場所" の表現です。 同期コンテキストは、 void
返される非同期関数の評価が開始、正常に完了、またはキャッチされない例外がスローされたときに通知されます。
これにより、コンテキストは、その下で実行されている void
返す非同期関数の数を追跡し、それらの中から出てくる例外を伝達する方法を決定できます。
ECMA C# draft specification