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 は、オプションの属性 (§22) のセットで構成され、その後にオプションの class_modifiers (§15.2.2) のセット、次に、オプションのpartial
修飾子 (§15.2.7)、その次にキーワード class
、その後、クラスに名前を付けた識別子、そしてオプションの type_parameter_list (§15.2.3)、オプションのclass_base 仕様 (§15.2.4)、オプションのtype_parameter_constraints_clauses (§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)。
partial 型宣言 (§15.2.7) にアクセシビリティ仕様 (public
、protected
、internal
、および private
修飾子を使用) が含まれている場合、その仕様はアクセシビリティ仕様を含む他のすべての部分と一致するものとします。 partial 型の一部にアクセシビリティ仕様が含まれている場合、その型には適切な既定のアクセシビリティ (§7.5.2) が付与されます。
次の小項目では、abstract
、sealed
、および static
修飾子について説明します。
15.2.2.2 抽象クラス
abstract
修飾子は、クラスが不完全であり、基底クラスとしてのみの使用を意図したものであることを示すために使用されます。
抽象クラスは、次の点で 非抽象クラスとは異なります。
- 抽象クラスを直接インスタンス化することはできません。また、抽象クラスで
new
演算子を使用するのはコンパイル時エラーです。 コンパイル時の型が abstract である変数と値を持つことはできますが、このような変数と値はnull
値であるか、または abstract 型から派生した非抽象クラスのインスタンスへの参照を含むかのいずれかである必要があります。 - 抽象クラスは、抽象メンバーを含む場合に許可されます (ただし必須ではありません)。
- 抽象クラスをシールにすることはできません。
非抽象クラスが抽象クラスから派生した場合、非抽象クラスには、継承されたすべての抽象メンバーの実際の実装が含まれるため、それらの抽象メンバーがオーバーライドされます。
例: 次のコード例の内容:
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
は非抽象にすることが許可されています (ただし、必須ではありません)。終了サンプル
クラスの partial 型宣言 (§15.2.7) の 1 つ以上の部分に abstract
修飾子が含まれている場合、そのクラスは抽象です。 それ以外の場合は、クラスは非抽象です。
15.2.2.3 シール クラス
sealed
修飾子は、クラスからの派生を防ぐために使用されます。 シール クラスが別のクラスの基底クラスとして指定されている場合、コンパイル時エラーが発生します。
また、シール クラスを抽象クラスにすることはできません。
注:
sealed
修飾子は、主に意図しない派生を防ぐために使用されますが、特定の実行時の最適化も可能にしてくれます。 特に、シール クラスには派生クラスが存在しないことがわかっているため、シール クラス インスタンスの仮想関数メンバーの呼び出しを非仮想呼び出しに変換できます。 注釈
クラスの partial 型宣言 (§15.2.7) の 1 つ以上の部分に 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) のみが含まれている必要があります。
注: すべての定数および入れ子になった型は、静的メンバーとして分類されます。 注釈
- 静的クラスに、
protected
、private protected
、またはprotected internal
のアクセス修飾子が指定されたメンバーを含めることはできません。
これらの制限のいずれかに違反するのは、コンパイル時エラーです。
静的クラスにはインスタンス コンストラクターがありません。 静的クラスでインスタンス コンストラクターを宣言することはできず、静的クラスには既定のインスタンス コンストラクター (§15.11.5) は提供されません。
静的クラスのメンバーは自動的には静的にならず、メンバー宣言には明示的に static
修飾子を含める必要があります (定数と入れ子になった型は除きます)。 静的外部クラス内でクラスが入れ子になっている場合、入れ子になったクラスは、static
修飾子を明示的に含まない限り、静的クラスではありません。
クラスの partial 型宣言 (§15.2.7) の 1 つ以上の部分に static
修飾子が含まれている場合、そのクラスは静的です。 それ以外の場合は、クラスは非静的です。
15.2.2.4.2 静的クラス型の参照
namespace_or_type_name (§7.8) は、次に該当する場合、静的クラスを参照できます。
-
namespace_or_type_name が、フォーム
T
の namespace_or_type_name 内のT.I
である場合、または -
namespace_or_type-name が、フォーム
T
の typeof_expression (§12.8.18) のtypeof(T)
である場合。
primary_expression (§12.8) は、次に該当する場合、静的クラスを参照できます。
-
primary_expression が、フォーム
E
の member_access (§12.8.7) のE.I
である場合。
その他のコンテキストで静的クラスを参照するのは、コンパイル時エラーです。
注: たとえば静的クラスが、基底クラス、メンバーの構成要素の型 (§15.3.7)、ジェネリック型引数、または型パラメーター制約として使用されるのはエラーです。 同様に、静的クラスは、配列型、新しい式、キャスト式、is 式、as 式、
sizeof
式、または既定値の式では使用できません。 注釈
15.2.3 型パラメーター
型パラメーターは、構築された型を作成するために指定された型引数のプレースホルダーを表す単純な識別子です。 対照的に、型引数 (§8.4.2) は、構築された型の作成時に型パラメーターに置き換える型です。
type_parameter_list
: '<' decorated_type_parameter (',' decorated_type_parameter)* '>'
;
decorated_type_parameter
: attributes? type_parameter
;
type_parameter は、§8.5 で定義されています。
クラス宣言の各型パラメーターは、そのクラスの宣言空間 (§7.3) に名前を定義します。 したがって、そのクラスの別の型パラメーターや、そのクラスで宣言されたメンバーと同じ名前を持つことはできません。 型パラメーターは、型自体と同じ名前を持つことはできません。
2 つの partial ジェネリック型宣言 (同じプログラム内) は、型パラメーターの数を指定する generic_dimension_specifier (§12.8.18) を含む同じ完全修飾名 (§7.8.3) を持つ場合、同じバインドされていないジェネリック型に影響します。 このような 2 つの partial 型宣言では、各型パラメーターに同じ名前を順番に指定する必要があります。
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
と見なされます。 部分クラス宣言に基底クラス仕様が含まれている場合、その基底クラス仕様は、基底クラス仕様を含む partial 型の他のすべての部分と同じ型を参照する必要があります。 部分クラスの一部に基底クラスの仕様が含まれている場合、その基底クラスは object
です。 クラスは、§15.3.4 で説明されているように、直接基底クラスからメンバーを継承します。
例: 次のコード例の内容:
class A {} class B : A {}
クラス
A
はB
の直接基底クラスと見なされ、B
はA
から派生しているとされます。A
は直接基底クラスを明示的に指定しないため、その直接基底クラスは暗黙的にobject
です。終了サンプル
ジェネリック型宣言内で宣言された入れ子になった型 (§15.3.9.7) を含む、構築されたクラス型の場合、ジェネリック クラス宣言で基底クラスが指定されている場合、構築された型の基底クラスは、基底クラス宣言の各 type_parameter に対して、構築された型の対応する type_argument を置き換えることによって取得されます。
例: ジェネリック クラス宣言が指定されている場合
class B<U,V> {...} class G<T> : B<string,T[]> {...}
構築された型
G<int>
の基底クラスはB<string,int[]>
となります。終了サンプル
クラス宣言で指定される基底クラスは、構築されたクラス型 (§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> {}
終了サンプル
クラスの型の直接基底クラスは、少なくとも、クラスの型自体と同程度にアクセス可能であるものとします (§7.5.5)。 たとえば、パブリック クラスがプライベート クラスまたは内部クラスから派生するのは、コンパイル時エラーです。
クラス型の直接基底クラスは、System.Array
、System.Delegate
、System.Enum
、System.ValueType
、または dynamic
の型のいずれにもすることはできません。 さらに、ジェネリック クラス宣言では、直接または間接基底クラスとして System.Attribute
を使用しないでください (§22.2.1)。
クラス A
の直接基底クラス仕様 B
の意味を決定する際に、B
の直接基底クラスは一時的に object
と見なされ、これにより、基底クラスの仕様の意味はそれ自体に再帰的に依存できなくなります。
例: 次の場合、
class X<T> { public class Y{} } class Z : X<Z.Y> {}
基底クラス仕様
X<Z.Y>
では、Z
の直接基底クラスがobject
と見なされるため、(§7.8 の規則では)Z
はメンバーY
を持たないと見なされることから、これはエラーになります。終了サンプル
クラスの基底クラスは、直接基底クラスとその基底クラスです。 言い換えると、基底クラスのセットは、直接基底クラス リレーションシップの推移閉包です。
例: 次の場合、
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
です。終了サンプル
クラス 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 は、
B.C
に循環して依存するB
(すぐ外側のクラス) に依存するA
(その直接基底クラス) に依存するため、コンパイル時エラーが発生します。終了サンプル
クラスは、その中で入れ子になっているクラスに依存しません。
例: 次のコード例の内容:
class A { class B : A {} }
B
はA
に依存します (A
は直接基底クラスとそのすぐ外側のクラスの両方であるため) が、A
はB
に依存しません (B
は基底クラスでも、A
の外側のクラスでもないため)。 したがって、この例は有効です。終了サンプル
シールされたクラスから派生することはできません。
例: 次のコード例の内容:
sealed class A {} class B : A {} // Error, cannot derive from a sealed class
クラス
B
は、シール クラスA
から派生しようとするため、エラーとなっています。終了サンプル
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
です。終了サンプル
通常、各部分は、その部分で宣言されたインターフェイスの実装を提供しますが、これは必須ではありません。 各部分では、別の部分で宣言されたインターフェイスの実装を提供できます。
例:
partial class X { int IComparable.CompareTo(object o) {...} } partial class X : IComparable { ... }
終了サンプル
クラス宣言で指定された基本インターフェイスは、構築されたインターフェイス型 (§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> {}
終了サンプル
インターフェイスの実装については、§18.6 で詳しく説明しています。
15.2.5 型パラメーターの制約
ジェネリック型とメソッドの宣言では、必要に応じて、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
、not null 制約notnull
、またはアンマネージド型制約unmanaged
を指定できます。 クラス型と参照型制約には、nullable_type_annotation を含めることができます。
セカンダリ制約には、interface_type または type_parameter、必要に応じて nullable_type_annotation を指定できます。 nullable_type_annotation の存在は、型引数が、制約を満たす null 非許容参照型に対応する null 許容参照型であることが許可されていることを示します。
参照型制約は、型パラメーターに使用される型引数が参照型であることを指定します。 すべてのクラス型、インターフェイス型、デリゲート型、配列型、および参照型であることが分かっている型パラメーター (以下で定義) は、この制約を満たしています。
クラス型、参照型制約、およびセカンダリ制約には、null 許容型注釈を含めることができます。 型パラメーターにこの注釈が存在するかどうかは、型引数に対する null 許容の期待値を示します。
- 制約に null 許容型注釈が含まれていない場合、型引数は null 非許容参照型であると想定されます。 型引数が null 許容参照型の場合、コンパイラは警告を発行することがあります。
- 制約に null 許容型注釈が含まれている場合は、null 非許容参照型と null 許容参照型の両方で制約が満たされます。
型引数の null 値許容は、型パラメーターの null 許容値と一致する必要はありません。 型パラメーターの null 許容が型引数の null 許容と一致しない場合、コンパイラから警告が発行されることがあります。
注: 型引数が null 許容参照型であることを指定するには、null 許容型注釈を制約として追加 (
T : class
またはT : BaseClass
を使用) せずに、ジェネリック宣言全体でT?
を使用して、型引数の対応する null 許容参照型を示します。 注釈
null 許容型の注釈 (?
) は、制約のない型引数では使用できません。
型引数が null 許容参照型 T
である場合に型パラメーター C?
の場合、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 許容型です。終了サンプル
not null 制約は、型パラメーターに使用される型引数を null 非許容値型または null 非許容参照型にすることを指定します。 null 非許容値型または null 非許容参照型ではない型引数が許可されますが、コンパイラによって診断警告が生成される場合があります。
notnull
はキーワードではないため、primary_constraint では not null 制約は常に class_typeと構文的にあいまいになります。 互換性上の理由から、名前 の名前検索 (notnull
) が成功した場合は、class_type
として扱われます。 それ以外の場合は、not null 制約として扱われます。
例: 次のクラスは、コンパイラによって発行される可能性がある警告を示す、さまざまな制約に対してさまざまな型引数を使用する方法を示しています。
#nullable enable public class C { } public class A<T> where T : notnull { } public class B1<T> where T : C { } public class B2<T> where T : C? { } class Test { static void M() { // nonnull constraint allows nonnullable struct type argument A<int> x1; // possible warning: nonnull constraint prohibits nullable struct type argument A<int?> x2; // nonnull constraint allows nonnullable class type argument A<C> x3; // possible warning: nonnull constraint prohibits nullable class type argument A<C?> x4; // nonnullable base class requirement allows nonnullable class type argument B1<C> x5; // possible warning: nonnullable base class requirement prohibits nullable class type argument B1<C?> x6; // nullable base class requirement allows nonnullable class type argument B2<C> x7; // nullable base class requirement allows nullable class type argument B2<C?> x8; } }
値型制約は、型パラメーターに使用される型引数が null 非許容値型であることを指定します。 null 非許容構造体型、列挙型、および値型制約を持つ型パラメーターはすべて、この制約を満たします。 値型として分類されますが、null 許容値型 (§8.3.12) は値型の制約を満たしません。 値型制約を持つ型パラメーターはまた、constructor_constraint を持ちませんが、constructor_constraint を持つ別の型パラメーターの型引数として使用できます。
注:
System.Nullable<T>
型は、T
の null 非許容値型制約を指定します。 したがって、T??
およびNullable<Nullable<T>>
の形式の再帰的に構築された型は禁止されています。 注釈
アンマネージド型制約は、型パラメーターに使用される型引数が null 非許容アンマネージド型 (§8.8) であることを指定します。
unmanaged
はキーワードではないため、primary_constraint では、アンマネージド制約は、class_type の使用で文法的に常に曖昧になります。 互換性上の理由から、名前 の名前検索 (unmanaged
) が成功した場合は、class_type
として扱われます。 それ以外の場合は、アンマネージド制約として扱われます。
ポインター型は、型引数として許可されることはなく、どの型制約も満たしません ( (アンマネージド型であってもアンマネージド制約を満たしません)。
制約がクラス型、インターフェイス型、または型パラメーターである場合、その型は、その型パラメーターに使用されるすべての型引数がサポートする最小の「基本型」を指定します。 構築された型またはジェネリック メソッドが使用されるたびに、コンパイル時に型パラメーターの制約に対して型引数がチェックされます。 指定する型引数は、§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
への同一型変換または暗黙的な参照変換、またはB
からA
への暗黙的な参照変換が必要です。 -
S
が型パラメーターU
にも依存し、U
に class_type 制約A
があり、T
に class_type 制約B
がある場合は、A
からB
への同一型変換または暗黙的な参照変換、またはB
からA
への暗黙的な参照変換が必要です。
S
は値型制約を持ち、T
は参照型制約を持つことができます。 実質的に、これにより、T
が、System.Object
、System.ValueType
、System.Enum
、および任意のインターフェイス型に制限されます。
型パラメーターの where
句にコンストラクター制約 (new()
の形式) が含まれている場合は、new
演算子を使用して型のインスタンスを作成できます (§12.8.17.2)。 コンストラクター制約を持つ型パラメーターに使用される型引数は、値型、パブリック パラメーターなしのコンストラクターを持つ非抽象クラス、または値型制約またはコンストラクター制約を持つ型パラメーターになります。
または の struct
を持つ unmanaged
が 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 { ... }
終了サンプル
型の は、次のように構築された 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
です。
型パラメーター のは次のように定義されます。
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
の有効な基底クラスは、 のセットの最も包含する型 (R
) です。 セットに包含型がない場合、T
の有効な基底クラスはobject
です。 整合性規則により、最も包含する型が確実に存在するようになります。
型パラメーターが、基本メソッドから制約を継承するメソッド型パラメーターの場合、有効な基底クラスは型の置換後に計算されます。
これらの規則により、有効な基底クラスが常に class_type であることを確実にすることができます。
型パラメーター の は次のように定義されます。
-
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(); }
IPrintable
は常にx
を実装するように制限されているため、T
のメソッドをIPrintable
で直接呼び出すことができます。終了サンプル
partial ジェネリック型宣言に制約が含まれている場合、その制約は、制約を含む他のすべての部分と一致する必要があります。 具体的には、制約を含む各部分には、同じ型パラメーターのセットに対する制約があり、各型パラメーターに対して、プライマリ、セカンダリ、およびコンストラクターの制約のセットは同等である必要があります。 2 つの制約セットに同じメンバーが含まれている場合、それらのセットは同等です。 型パラメーター制約を指定している部分が partial ジェネリック型にない場合、型パラメーターは制約なしと見なされます。
例:
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 つ) が、同じ型パラメーターのセットに対して同じプライマリ制約、セカンダリ制約、コンストラクター制約のセットを効果的に指定するため、正しいです。
終了サンプル
15.2.6 クラス本体
クラスの class_body は、そのクラスのメンバーを定義します。
class_body
: '{' class_member_declaration* '}'
;
15.2.7 partial 型宣言
修飾子 partial
は、クラス、構造体、またはインターフェイス型を複数の部分で定義するときに使用されます。
partial
修飾子はコンテキスト キーワード (§6.4.4) であり、キーワード class
、struct
、および interface
の直前で特別な意味を持ちます。 (partial 型には、partial メソッド宣言 (§15.6.9) が含まれる場合があります。
partial 型宣言の各部分は、partial
修飾子を含み、他の部分と同じ名前空間または包含型で宣言される必要があります。
partial
修飾子は、型宣言の追加の部分が他の場所に存在する可能性があることを示しますが、このような追加部分の存在は必須ではありません。型の 1 つの宣言で partial
修飾子を含めることができます。 partial 型の 1 つだけの宣言が、基底クラスまたは実装されたインターフェースを含めることは可能です。 ただし、基底クラスまたは実装されたインターフェイスのすべての宣言は、指定された型引数の null 許容を含め、一致する必要があります。
partial 型のすべての部分は、コンパイル時にマージできるように一緒にコンパイルされる必要があります。 partial 型では、コンパイル済みの型を拡張することは明示的に許可されていません。
入れ子にされた型は、partial
修飾子を使用して複数の部分で宣言できます。 通常、包含型も 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; }
終了サンプル
partial 型宣言の異なる部分で型または型パラメーターに指定された属性の処理については、§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) を作成し、class_declaration に直接含まれる type_parameter と class_member_declaration は、この宣言空間に新しいメンバーを導入します。 class_member_declaration には、次の規則が適用されます。
インスタンス コンストラクター、ファイナライザー、および静的コンストラクターは、すぐ外側のクラスと同じ名前を持つ必要があります。 他のすべてのメンバーは、すぐ外側のクラスの名前とは異なる名前を持つ必要があります。
クラス宣言の type_parameter_list の型パラメーターの名前は、同じ type_parameter_list 内の他のすべての型パラメーターの名前とは異なり、クラスの名前とクラスのすべてのメンバーの名前とも異なっている必要があります。
型の名前は、同じクラスで宣言されているすべての非型メンバーの名前とは異なるものでなければなりません。 2 つ以上の型宣言が同じ完全修飾名を共有する場合、これらの宣言は
partial
修飾子 (§15.2.7) を持ち、これらの宣言を組み合わせて 1 つの型が定義されるものとします。
注: 型宣言の完全修飾名は型パラメーターの数をエンコードするため、型パラメーターの数が異なる限り、2 つの異なる型は同じ名前を共有できます。 注釈
定数、フィールド、プロパティ、またはイベントの名前は、同じクラスで宣言されている他のすべてのメンバーの名前と異なるものでなければなりません。
メソッドの名前は、同じクラスで宣言されているすべての他の非メソッドの名前とは異なるものでなければなりません。 さらに、メソッドのシグネチャ (§7.6) は、同じクラスで宣言されている他のすべてのメソッドのシグネチャと異なる必要があり、同じクラスで宣言されている 2 つのメソッドには、
in
、out
、およびref
によってのみ異なるシグネチャは含まれていないものとします。インスタンス コンストラクターのシグネチャは、同じクラスで宣言されている他のすべてのインスタンス コンストラクターのシグネチャとは異なっている必要があります。また、同じクラスで宣言された 2 つのコンストラクターには、
ref
とout
によってのみ異なるシグネチャは含まれていないものとします。インデクサーのシグネチャは、同じクラスで宣言されている他のすべてのインデクサーのシグネチャとは異なる必要があります。
演算子のシグネチャは、同じクラスで宣言されている他のすべてのインデクサーの演算子とは異なる必要があります。
クラスの継承されたメンバー (§15.3.4) は、クラスの宣言空間の一部ではありません。
注: したがって、派生クラスは、継承されたメンバーと同じ名前またはシグネチャを持つメンバーを宣言できます (継承されたメンバーは実質的に非表示になります)。 注釈
複数の部分で宣言された型のメンバーのセット (§15.2.7) は、各部分で宣言されたメンバーの和集合です。 型宣言のすべての部分の本体は同じ宣言空間 (§7.3) を共有し、各メンバーのスコープ (§7.7) はすべての部分の本体まで拡張します。 メンバーのアクセシビリティ ドメインには、常にエンクロージング型のすべての部分が含まれます。ある部分で宣言されたプライベート メンバーは、別の部分から自由にアクセスできます。 そのメンバーに partial
修飾子がない限り、型の複数の部分で同じメンバーを宣言するのはコンパイル時エラーです。
例:
partial class A { int x; // Error, cannot declare x more than once partial void M(); // Ok, defining partial method declaration partial class Inner // Ok, Inner is a partial type { int y; } } partial class A { int x; // Error, cannot declare x more than once partial void M() { } // Ok, implementing partial method declaration partial class Inner // Ok, Inner is a partial type { int z; } }
終了サンプル
フィールドの初期化順序は 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
終了サンプル
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) {...}
ジェネリック クラス宣言 の
a
メンバーGen
の型は「T
の 2 次元配列」であるため、上記の構築された型のメンバーa
の型は「int
の 1 次元配列の 2 次元配列」、つまりint[,][]
となります。終了サンプル
インスタンス関数メンバー内では、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 } }
終了サンプル
15.3.4 継承
クラスは、その直接基底クラスのメンバーを継承します。 継承とは、インスタンス コンストラクター、ファイナライザー、および基底クラスの静的コンストラクターを除いた直接基底クラスのすべてのメンバーが、クラスに暗黙的に含まれることを意味します。 継承の重要な点を以下に挙げています。
継承は推移的です。
C
がB
から派生し、B
がA
から派生している場合、C
は、B
で宣言されたメンバーだけでなく、A
で宣言されたメンバーも継承します。派生クラスは、その直接基底クラスを拡張します。 派生クラスは、継承するメンバーに新しいメンバーを追加できますが、継承されたメンバーの定義を削除することはできません。
インスタンス コンストラクター、ファイナライザー、および静的コンストラクターは継承されませんが、宣言されたアクセシビリティ (§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>
には、型パラメーターint
の型引数G(string s)
を置き換えることによって取得された、継承されていないメンバーパブリックint
T
があります。D<int>
には、クラス宣言B
から継承されたメンバーもあります。 この継承されたメンバーは、最初に基底クラス仕様のB<int[]>
のD<int>
でint
を置き換えることによって、T
の基底クラス型B<T[]>
を決定します。 次に、B
の型引数として、int[]
がU
のpublic U F(long index)
に置き換わり、継承されたメンバーpublic int[] F(long index)
が生成されます。終了サンプル
15.3.5 新しい修飾子
class_member_declaration は、継承されたメンバーと同じ名前またはシグネチャを持つメンバーを宣言できます。 この場合、派生クラス メンバーは基底クラス メンバーを非表示にするものとされています。 メンバーが継承されたメンバーを非表示にする正確な仕様については、§7.7.2.3 を参照してください。
継承されたメンバー M
は、 がアクセス可能であり、 を既に非表示にしている他の継承されたアクセス可能なメンバー N が存在しない場合、M
とみなされます。 継承されたメンバーを暗黙的に隠すことはエラーとは見なされませんが、派生クラス メンバーの宣言に、派生メンバーで基底メンバーを隠蔽することを明示的に示す new
修飾子が含まれていない限り、コンパイラから警告が発行されます。 入れ子になった型の partial 宣言の 1 つ以上の部分 (§15.2.7) に new
修飾子が含まれている場合は、入れ子になった型で使用可能な継承メンバーが非表示になっていても、警告は発行されません。
使用可能な継承されたメンバーを非表示にしない宣言に new
修飾子が含まれている場合は、その旨を伝える警告が発行されます。
15.3.6 アクセス修飾子
class_member_declaration は、許可されている種類の宣言されたアクセシビリティ (、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 静的メンバーとインスタンス メンバー
クラスのメンバーは、静的メンバーまたはインスタンス メンバーです。
注: 一般に、静的メンバーはクラスに属し、インスタンス メンバーはオブジェクト (クラスのインスタンス) に属していると考えると便利です。 注釈
フィールド、メソッド、プロパティ、イベント、演算子、またはコンストラクターの宣言に static
修飾子が含まれている場合は、静的メンバーを宣言します。 さらに、定数または型の宣言では、静的メンバーを暗黙的に宣言します。 静的メンバーには、次の特性があります。
- 静的メンバー
M
が member_access (§12.8.7) の形式E.M
で参照される場合、E
はメンバーM
を持つ型を表します。E
がインスタンスを示す場合はコンパイル時エラーです。 - 非ジェネリック クラスの静的フィールドは、1 つの格納場所を正確に識別します。 非ジェネリック クラスのインスタンスがいくつ作成されても、静的フィールドのコピーは 1 つだけです。 個別のクローズドの構築型 (§8.4.3) は、クローズドの構築型のインスタンスの数に関係なく、独自の静的フィールドのセットを持ちます。
- 静的関数メンバー (メソッド、プロパティ、イベント、演算子、またはコンストラクター) は特定のインスタンスで動作せず、このような関数メンバーでその特定のインスタンスを参照するのはコンパイル時エラーです。
フィールド、メソッド、プロパティ、イベント、インデクサー、コンストラクター、またはファイナライザーの宣言に静的修飾子が含まれていない場合は、インスタンス メンバーを宣言します。 (インスタンス メンバーは、非静的メンバーと呼ばれることもあります。) インスタンス メンバーには、次の特性があります。
- インスタンス メンバー
M
が、形式 の member_access (E.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) では、インスタンス メンバーはインスタンスを介して、静的メンバーは型を介してアクセスされるべきであることを示しています。終了サンプル
15.3.9 入れ子にされた型
15.3.9.1 全般
クラスや構造体の中で宣言された型は、入れ子型と呼ばれます。 コンパイル単位または名前空間内で宣言された型は、非入れ子型と呼ばれます。
例: 次の例では、
class A { class B { static void F() { Console.WriteLine("A.B.F"); } } }
クラス
B
は、クラスA
内で宣言されているため入れ子型であり、クラスA
はコンパイル単位内で宣言されているため、非入れ子型です。終了サンプル
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
を宣言します。終了サンプル
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(); } }
M
で定義されているメソッドM
を非表示にする入れ子になったクラスBase
を示しています。終了サンプル
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
のインスタンス メンバーへの後続のアクセスを提供します。終了サンプル
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(); }
入れ子になったクラス
C
を含むクラスNested
を示しています。Nested
内では、メソッドG
は、F
で定義された静的メソッドC
を呼び出し、F
にはプライベートで宣言されたアクセシビリティがあります。終了サンプル
入れ子型は、その包含型の基本型で定義されている保護されたメンバーにもアクセスできます。
例: 次のコード例の内容:
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
は、F
のインスタンスを介して呼び出すことによって、Derived
の基底クラス (Base
) で定義されている保護されたメソッドDerived
にアクセスします。終了サンプル
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 } }
終了サンプル
プログラミング スタイルとしては不適切ですが、入れ子型の型パラメーターによって、外側の型で宣言されたメンバーまたは型パラメーターを非表示にできます。
例:
class Outer<T> { class Inner<T> // Valid, hides Outer's T { public T t; // Refers to Inner's T } }
終了サンプル
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# 言語機能へのアクセスを取得または設定するためのメソッド名として通常の識別子を使用して、他の言語が相互運用できるようにします。
- 1 つの準拠コンパイラで受け入れられるソースが、すべての C# 実装で受け入れられるようにするために、すべての C# 実装で予約済みメンバー名の詳細に一貫性を持たせます。
注釈
ファイナライザー (§15.13) の宣言により、シグネチャも予約されます (§15.3.10.5)。
一部の名前は、演算子メソッド名として使用するために予約されています (§15.3.10.6)。
15.3.10.2 プロパティ用に予約されたメンバー名
P
型のプロパティ (T
) の場合、次のシグネチャが予約されています。
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
終了サンプル
15.3.10.3 イベント用に予約されたメンバー名
デリゲート型 E
のイベント (T
) の場合、次のシグネチャが予約されます。
void add_E(T handler);
void remove_E(T handler);
15.3.10.4 インデクサー用に予約されたメンバー名
パラメーター リスト を持つ型 T
のインデクサー (L
) の場合、次のシグネチャが予約されます。
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 は、一連の属性 (§22)、new
修飾子 (§15.3.5)、および許可されている宣言されたアクセシビリティのいずれか (§15.3.6) を含むことができます。 属性と修飾子は、constant_declaration によって宣言されたすべてのメンバーに適用されます。 定数は静的メンバーと見なされますが、constant_declaration は static
修飾子を要求することも、許可することもありません。 定数宣言で同じ修飾子が複数回出現するのはエラーです。
constant_declaration の型は、宣言で導入されるメンバーの型を指定します。 型の後に、それぞれが新しいメンバーを導入する constant_declarator の一覧 (§13.6.3) が続きます。
constant_declarator は、メンバーに名前を付ける識別子と、その後に続く "=
" トークンと、メンバーの値を指定する 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
メンバー宣言、属性、およびその他の定数宣言が含まれます。 注釈
注: §12.23 で説明されているように、constant_expression はコンパイル時に完全に評価できる式です。 reference_type の値が
string
以外で null 以外の値を作成する唯一の方法は、new
演算子を適用することですが、new
演算子は constant_expression で許可されていないため、 以外のstring
の定数の値に使用できるのはnull
のみです。 注釈
定数値のシンボリック名が必要な場合、その値の型が定数宣言で許可されていない場合、または constant_expression によってコンパイル時に値を計算できない場合は、代わりに読み取り専用フィールド (§15.5.3) を使用できます。
注:
const
とreadonly
のバージョン管理セマンティクスは異なります (§15.5.3.3)。 注釈
複数の定数を宣言する定数宣言は、同じ属性、修飾子、および型を持つ 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; }
終了サンプル
依存関係が循環的でない限り、定数は同じプログラム内の他の定数に依存できます。
例: 次のコード例の内容:
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
を生成する必要があります。終了サンプル
定数宣言は他のプログラムの定数に依存する場合がありますが、このような依存関係は一方向でのみ可能です。
例: 上記の例を参照すると、
A
とB
が別々のプログラムで宣言されている場合、A.X
はB.Z
に依存できますが、B.Z
は同時にA.Y
に依存することはできません。 終了サンプル
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 には、一連の 属性 (§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 の型は、宣言で導入されるメンバーの型を指定します。 型の後に、各々が新しいメンバーを導入する variable_declarator の一覧が続きます。
variable_declarator は、そのメンバーに名前を付ける識別子、必要に応じて "=
" トークンと、そのメンバーの初期値を提供する variable_initializer (§15.5.6) で構成されます。
フィールドの型は、少なくともフィールド自体と同程度にアクセス可能 (§7.5.5) である必要があります。
フィールドの値は、simple_name (§12.8.4)、member_access (§12.8.7)、または base_access (§12.8.15) を使用して式で取得されます。 読み取り専用でないフィールドの値は、代入 (§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; }
終了サンプル
15.5.2 静的フィールドとインスタンス フィールド
フィールド宣言に static
修飾子が含まれている場合、宣言によって導入されたフィールドは 静的フィールドです。
static
修飾子が存在しない場合、宣言によって導入されたフィールドは、インスタンス フィールドです。 静的フィールドとインスタンス フィールドは、C# でサポートされる複数の種類の変数 (§9) の 2 つであり、それぞれ静的変数およびインスタンス変数と呼ばれることもあります。
§15.3.8 で説明したようにクラスの各インスタンスには、クラスのインスタンス フィールドの完全なセットが含まれますが、クラスのインスタンス数またはクローズドの構築型の数に関係なく、非ジェネリック クラスまたはクローズドの構築型ごとに静的フィールドのセットは 1 つだけです。
15.5.3 読み取り専用フィールド
15.5.3.1 全般
field_declaration に readonly
修飾子が含まれている場合、宣言によって導入されたフィールドは読み取り専用フィールドです。 読み取り専用フィールドへの直接代入は、その宣言の一部として、または同じクラスのインスタンス コンストラクターまたは静的コンストラクター内でのみ行うことができます。 (これらのコンテキストでは、読み取り専用フィールドを複数回割り当てることができます。) 具体的には、読み取り専用フィールドへの直接代入は、次のコンテキストでのみ許可されます。
- フィールドを (宣言に variable_initializer を含めることで) 導入する variable_declarator。
- インスタンス フィールドの場合は、フィールド宣言が含まれるクラスのインスタンス コンストラクター内。静的フィールドの場合は、フィールド宣言が含まれるクラスの静的コンストラクター内。 これらは、読み取り専用フィールドを出力パラメーターまたは参照パラメーターとして渡すことが有効な唯一のコンテキストです。
その他のコンテキストで読み取り専用フィールドに代入したり、出力パラメーターまたは参照パラメーターとして渡そうとすると、コンパイル時エラーになります。
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
として宣言すると、同じ効果が得られます。終了サンプル
15.5.3.3 定数と静的読み取り専用フィールドのバージョン管理
定数フィールドと読み取り専用フィールドでは、異なるバイナリ バージョン管理セマンティクスがあります。 式が定数を参照する場合、定数の値はコンパイル時に取得されますが、式が読み取り専用フィールドを参照する場合、フィールドの値は実行時まで取得されません。
例: 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
が再コンパイルされた場合、Console.WriteLine
が再コンパイルされていない場合でも、Program2
ステートメントによって新しい値が出力されます。 ただし、X
が定数であった場合、X
の値はProgram2
のコンパイル時に取得され、Program1
が再コンパイルされるまでProgram2
の変更の影響を受けることはありません。終了サンプル
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
の型 -
enum_base 型が、、
byte
、sbyte
、short
、ushort
、またはint
であるuint
例: 例
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
と呼ばれる非揮発性フィールドに値を格納し、揮発性フィールドtrue
にfinished
を格納します。 メイン スレッドは、フィールドfinished
がtrue
に設定されるのを待ってから、フィールドresult
を読み取ります。finished
はvolatile
を宣言されているため、メイン スレッドはフィールド143
からresult
値を読み取ります。 フィールドfinished
がvolatile
を宣言されていなかった場合は、result
への書き込みが への書き込みfinished
にメインスレッドから可視化され、メインスレッドがフィールドresult
から値 0 を読み取る可能性がありました。finished
をvolatile
フィールドとして宣言すると、このような不整合を回避できます。終了サンプル
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
の両方が自動的に既定値に初期化されるためです。終了サンプル
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
への代入がインスタンス フィールド初期化子の実行時に発生するためです。終了サンプル
§15.5.5 で説明されている既定値の初期化は、変数初期化子を持つフィールドを含むすべてのフィールドに対して行われます。 したがって、クラスが初期化されると、そのクラス内のすべての静的フィールドはまず既定値に初期化され、その後、静的フィールド初期化子がテキスト順に実行されます。 同様に、クラスのインスタンスが作成されると、そのインスタンス内のすべてのインスタンス フィールドが最初に既定値に初期化され、次にインスタンス フィールド初期化子がテキスト順で実行されます。 同じ型の複数の partial 型宣言にフィールド宣言がある場合、部分の順序は指定されません。 ただし、各部分内では、フィールド初期化子が順番に実行されます。
変数初期化子を持つ静的フィールドが、既定値の状態であることは可能です。
例: ただし、これはスタイル上の理由から強く推奨されません。 例
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
に初期化されます。終了サンプル
15.5.6.2 静的フィールドの初期化
クラスの静的フィールド変数初期化子は、クラス宣言 (§15.5.6.1) に表示されるテキストの順序で実行される代入のシーケンスに対応します。 partial クラス内での、「テキストの順序」の意味は §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
の静的コンストラクターとフィールド初期化子よりも先に実行されるためです。終了サンプル
15.5.6.3 インスタンス フィールドの初期化
クラスのインスタンス フィールド変数初期化子は、そのクラスのいずれかのインスタンス コンストラクター (§15.11.3) へのエントリの直後に実行される代入のシーケンスに対応します。 partial クラス内での、「テキストの順序」の意味は §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
の変数初期化子は、作成中のインスタンスのメンバーを参照するため、コンパイル時エラーになります。終了サンプル
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'
| 'readonly' // direct struct members only
| 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) でのみ使用できます。
- method_body を認識する際、null_conditional_invocation_expression と expression の両方の代替が適用可能な場合、前者を選択する必要があります。
注:ここでの代替手段の重複と優先順位は、説明的な利便性のみを目的としています。文法規則を詳しく説明して、重複を取り除く可能性があります。 ANTLR やその他の文法システムでは、同じ利便性が採用されているため、method_body には指定されたセマンティクスが自動的に適用されます。 注釈
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
(§15.6.7)、extern
(§15.6.8) および async
(§15.14) が含まれる場合があります。 さらに struct_declaration によって直接含まれた method_declaration には、readonly
の修飾子(§16.4.12)を適用することができます。
次のすべてが当てはまる場合、宣言には修飾子の有効な組み合わせがあります。
- 宣言に、アクセス修飾子の有効な組み合わせが含まれている (§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
が存在する場合、メソッドは戻り値参照で、必要に応じて読み取り専用となる変数参照を返します。 - それ以外の場合、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
でなければならないか、メソッドが値によって返され、戻り値の型は タスク型 (§15.14.1) になります。
戻り値参照メソッド宣言の ref_return_type は、メソッドによって返される variable_reference によって参照される変数の型を指定します。
ジェネリック メソッドは、宣言に type_parameter_list が含まれるメソッドです。 これにより、メソッドの型パラメーターが指定されます。 省略可能な type_parameter_constraints_clause では、型パラメーターの制約を指定します。
ジェネリック method_declarationは、 override
修飾子を使用するか、明示的なインターフェイス メンバーの実装に対して、オーバーライドされたメソッドまたはインターフェイス メンバーからそれぞれ型パラメーター制約を継承します。 このような宣言には、type_parameter_constraints_clauseが含まれており、これはprimary_constraintとしてclass
およびstruct
を含みます。このコンテキストでの意味は、それぞれメソッドのオーバーライドと明示的なインターフェイス実装に関して§15.6.5および§18.6.2に定義されています。
member_name は、メソッドの名前を指定します。 メソッドが明示的なインターフェイス メンバー実装 (§18.6.2) でない限り、member_name は単に識別子です。
明示的なインターフェイス メンバー実装の場合、member_name は、interface_type とその後に ".
" と識別子が続く形式です。 この場合、宣言には、(場合によって) extern
または async
以外の修飾子を含めてはなりません。
省略可能な parameter_list は、メソッドのパラメーターを指定します (§15.6.2)。
return_type または ref_return_type、およびメソッドの parameter_list で参照される各型は、少なくともメソッド自体と同等のアクセスが可能である必要があります (§7.5.5)。
値による戻り値 メソッドまたは returns-no-value メソッドの method_body は、セミコロン、ブロック本体、または式本体のいずれかです。 ブロック本体は、メソッドの呼び出し時に実行するステートメントを指定するブロックで構成されます。 式本体は、=>
、null_conditional_invocation_expression または expression、およびセミコロンで構成され、メソッドの呼び出し時に実行する単一の式を表します。
abstract メソッドと extern メソッドの場合、method_body は単にセミコロンで構成されます。 partial メソッドの場合、method_body はセミコロン、ブロック本体、または式本体で構成できます。 他のすべてのメソッドでは、method_body はブロック本体または式本体のいずれかです。
method_body がセミコロンで構成されている場合、宣言には async
修飾子は含まれません。
戻り値参照メソッドの 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 内で型を形成するために使用できますが、属性では使用できません。
すべてのパラメーターと型パラメーターの名前は異なっている必要があります。
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 は、省略可能な属性のセット (§22) (省略可能な in
、out
、ref
、または this
修飾子、型、識別子、および省略可能な 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
-
new S()
が値型となる、S
形式の式。 -
default(S)
が値型となる、S
形式の式。
式は、パラメーターの型への ID または null 許容変換によって暗黙的に変換できる必要があります。
オプション パラメーターが実装する部分メソッド宣言 (§15.6.9)、明示的なインターフェース メンバー実装 (§18.6.2)、単一パラメーター インデクサー宣言 (§15.9)、または演算子宣言 (§15.10.1) に含まれている場合、これらのメンバーは引数を省略して呼び出すことができないため、コンパイラーは警告を出すべきです。
parameter_array は、省略可能な属性のセット (§22)、params
修飾子、array_type、および識別子で構成されます。 パラメーター配列は、指定された名前を持つ特定の配列型の 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
では、i
は必須のref
パラメーターであり、d
は必須の値パラメーターで、b
、s
、o
、およびt
は省略可能な値パラメーター、そしてa
はパラメーター配列です。終了サンプル
メソッド宣言では、パラメーターと型パラメーター用に個別の宣言空間 (§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
修飾子は含まれません。 注釈
15.6.2.2 値パラメーター
修飾子なしで宣言されたパラメーターは、値パラメーターです。 値パラメーターは、メソッド呼び出しで指定された対応する引数から初期値を取得するローカル変数です。
明確な代入の規則については、§9.2.5 を参照してください。
メソッド呼び出しにおける対応する引数は、パラメーター型に暗黙的に変換可能な式 (§10.2) でなければなりません。
メソッドは、値パラメーターに新しい値を代入することができます。 このような代入は、値パラメーターによって表されるローカル ストレージの場所にのみ影響します。メソッドの呼び出しで指定された実際の引数には影響しません。
15.6.2.3 参照渡しパラメーター
15.6.2.3.1 全般
入力、出力、および参照パラメーターは、参照渡しパラメーターです。 参照渡しパラメーターはローカル参照変数 (§9.7) であり、最初の参照先は、メソッド呼び出しで指定された対応する引数から取得されます。
注: 参照渡しパラメーターの参照先は、ref 代入 (
= ref
) 演算子を使用して変更できます。
パラメーターが参照渡しパラメーターの場合、メソッド呼び出しの対応する引数は、対応するキーワード (in
、ref
、または out
) と、その後に続くパラメーターと同じ型の variable_reference (§9.5) で構成されます。 ただし、パラメーターが in
パラメーターの場合、引数は、その引数式から対応するパラメーターの型への暗黙的な変換 (§10.2) が存在する式とすることができます。
反復子 (§15.15) または非同期関数 (§15.14) として宣言された関数では、参照によるパラメーターは使用できません。
複数の参照渡しパラメーターを受け取るメソッドでは、複数の名前が同じストレージの場所を示す可能性があります。
15.6.2.3.2 入力パラメーター
in
修飾子で宣言されたパラメーターは、入力パラメーターです。 入力パラメーターに対応する引数は、メソッド呼び出しの時点ですでに存在する変数、またはメソッド呼び出しの実装 (§12.6.2.3) によって作成された変数です。 明確な代入の規則については、§9.2.5 を参照してください。
入力パラメーターの値を変更するのはコンパイル時エラーです。
注: 入力パラメーターの主な目的は、効率化です。 メソッド パラメーターの型が (メモリ要件の観点から) 大きな構造体である場合、メソッド呼び出し時に引数の値全体をコピーする必要を回避できる点が便利です。 入力パラメーターを使用すると、メソッドは、メモリ内の既存の値を参照しながら、それらの値に対する望ましくない変更に対する保護を提供できます。 注釈
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
Swap
でのMain
の呼び出しでは、x
はi
を表し、y
はj
を表します。 したがって、呼び出しは、i
とj
の値を入れ替える作用があります。終了サンプル
例: 次のコード例の内容:
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); } }
F
でのG
の呼び出しは、s
とa
の両方に対しb
への参照を渡します。 したがって、その呼び出しでは、名前s
、a
、およびb
はすべて同じストレージの場所を参照し、3 つの代入はすべてインスタンス フィールドs
を変更します。終了サンプル
struct
型の場合、インスタンス メソッド、インスタンス アクセサー (§12.2.1)、またはコンストラクター初期化子を持つインスタンス コンストラクター内では、this
キーワードは構造体型 (§12.8.14) の参照パラメーターとまったく同じように動作します。
15.6.2.3.4 出力パラメーター
out
修飾子で宣言されたパラメーターは、出力パラメーターです。 明確な代入の規則については、§9.2.7 を参照してください。
partial メソッド (§15.6.9) として宣言されたメソッドには、出力パラメーターを含めることはできません。
注: 出力パラメーターは、通常、複数の戻り値を生成するメソッドで使用されます。 注釈
例:
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
に渡される前に未代入の状態であってもよい点と、呼び出し後には確実に代入されたものと見なされる点に留意してください。終了サンプル
15.6.2.4 パラメーター配列
params
修飾子で宣言されたパラメーターは、パラメーター配列です。 パラメーター リストにパラメーター配列が含まれている場合、そのパラメーターはリスト内の最後のパラメーターであり、1 次元配列型である必要があります。
例:
string[]
型とstring[][]
型はパラメーター配列の型として使用できますが、string[,]
型は使用できません。 終了サンプル
注:
params
修飾子を修飾子のin
、out
、またはref
と組み合わせすることはできません。 注釈
パラメーター配列では、メソッド呼び出しにおいて次の 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[] {});
終了サンプル
オーバーロード解決を実行する場合、パラメーター配列を持つメソッドは、通常の形式または展開形式 (§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 番目のメソッドの呼び出しでは通常のメソッドが選択されます。 クラスがパラメーター配列を持つメソッドを宣言する場合、展開形式の一部を通常のメソッドとして含めることも珍しくありません。 これを行うことで、パラメーター配列を持つメソッドの拡張形式が呼び出されたときに発生する配列インスタンスの代入を回避できます。
終了サンプル
配列は参照型であるため、パラメーター配列に渡される値は
null
にすることができます。例: 例
class Test { static void F(params string[] array) => Console.WriteLine(array == null); static void Main() { F(null); F((string) null); } }
この例では、次のように出力されます。
True False
2 番目の呼び出しでは、
False
と同等であるためF(new string[] { null })
が生成され、1 つの null 参照を含む配列が渡されます。終了サンプル
パラメーター配列の型が 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[]
への参照) で初期化されます。終了サンプル
15.6.3 静的メソッドとインスタンス メソッド
メソッド宣言に static
修飾子が含まれている場合、そのメソッドは静的メソッドと見なされます。
static
修飾子が存在しない場合、メソッドはインスタンス メソッドと見なされます。
静的メソッドは特定のインスタンスでは動作しません。静的メソッドで this
を参照するのはコンパイル時エラーです。
インスタンス メソッドはクラスの特定のインスタンスで動作し、this
として (§12.8.14) そのインスタンスへのアクセスが可能です。
静的メンバーとインスタンス メンバーの違いについては、§15.3.8 で詳しく説明しています。
15.6.4 仮想メソッド
インスタンス メソッドの宣言に virtual 修飾子が含まれている場合、そのメソッドは仮想メソッドと見なされます。 virtual 修飾子が存在しない場合、そのメソッドは非仮想メソッドと見なされます。
非仮想メソッドの実装は不変です。メソッドが宣言されたクラスのインスタンス上で呼び出された場合でも、派生クラスのインスタンス上で呼び出された場合でも、実装は同じです。 これに対し、仮想メソッドの実装は派生クラスに置き換えることができます。 継承された仮想メソッドの実装を上書きするプロセスは、そのメソッド (§15.6.5) のオーバーライドと呼ばれます。
仮想メソッドの呼び出しでは、その呼び出しが行われるインスタンスの実行時の型によって、呼び出す実際のメソッドの実装が決定します。 非仮想メソッドの呼び出しでは、インスタンスのコンパイル時の型が決定要因となります。 正確に言うと、N
という名前のメソッドが、コンパイル時の型 A
と実行時の型 C
(R
が R
または C
から派生したクラス) を持つインスタンスに、引数リスト C
を使用して呼び出されると、呼び出しは次のように処理されます。
- バインド時に、オーバーロードの解決が
C
、N
、およびA
に適用され、M
によって宣言および継承された一連のメソッドから特定のメソッドC
を選択します。 これについては、§12.8.10.2 で説明しています。 - その後、実行時に:
-
M
が非仮想メソッドの場合は、M
が呼び出されます。 - それ以外の場合は、
M
は仮想メソッドであり、M
に関してR
の最も派生した実装が呼び出されます。
-
クラスで宣言されたり継承されたりしたすべての仮想メソッドには、そのクラスに関して、そのメソッドの最も派生した実装が存在します。
M
クラスに関してR
仮想メソッドの最も派生した実装は、次のように決定されます。
-
R
にM
の仮想宣言の導入が含まれている場合は、これがM
に関するR
の最も派生した実装です。 - それ以外の場合、
R
にM
のオーバーライドが含まれている場合は、これがM
に関するR
の最も派生した実装です。 - 上記以外の場合は、
M
に関するR
の最も派生した実装は、M
の直接基底クラスに関するR
の最も派生した実装と同じです。
例: 次の例は、仮想メソッドと非仮想メソッドの違いを示しています。
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
は、継承した をF
新しい非仮想メソッドF
を導入し、また継承したメソッド をG
しています。 この例を実行すると、次の出力が生成されます。A.F B.F B.G B.G
ステートメント
a.G()
はB.G
ではなく、A.G
を呼び出していることに注意してください。 これは、インスタンスのコンパイル時の型 (B
) ではなく、インスタンスのランタイム型 (A
) によって、呼び出す実際のメソッドの実装が決定されるためです。終了サンプル
メソッドは継承されたメソッドを非表示にできるため、クラスに同じシグネチャを持つ複数の仮想メソッドを含めることができます。 最も派生したメソッドを除くすべてのメソッドが非表示になっているため、あいまいさの問題が生じません。
例: 次のコード例の内容:
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
のインスタンスにアクセスすることで行うことが可能です。終了サンプル
15.6.5 メソッドのオーバーライド
インスタンス メソッドの宣言に override
修飾子が含まれている場合、そのメソッドはオーバーライド メソッドと見なされます。 オーバーライド メソッドは、継承された仮想メソッドを同じシグネチャでオーバーライドします。 仮想メソッド宣言は新しいメソッドを導入するのに対し、オーバーライド メソッド宣言では、そのメソッドの新しい実装を提供することで既存の継承された仮想メソッドを特殊化します。
オーバーライド宣言によってオーバーライドされるメソッドは、オーバーライドされる基本メソッドと呼ばれます。クラス M
で宣言されたオーバーライド メソッド C
の場合、オーバーライドされた基本メソッドは、C
の各基底クラスの調査を、C
の直接基底クラスから開始して、連続する各直接基底クラスで順次行うことによって決定されます。これは、指定された基底クラス型において、型引数の置換後に M
とシグネチャが一致するアクセス可能なメソッドが少なくとも 1 つ見つかるまで続行されます。 オーバーライドされた基本メソッドを特定する目的で、メソッドは次の条件を満たす場合に、アクセス可能と見なされます。public
である場合、protected
である場合、protected internal
である場合、あるいはメソッドが internal
または private protected
であり、C
と同じプログラムで宣言されている場合。
オーバーライドするメソッドは、オーバーライドされた基本メソッドの 任意のtype_parameter_constraints_clauseを継承します。
オーバーライド宣言で次のすべてが当てはまる場合を除き、コンパイル時エラーが発生します。
- オーバーライドされた基本メソッドが、前述のように特定される。
- このようなオーバーライドされた基本メソッドが 1 つだけ存在する。 この制限は、基底クラスの型が構築された型であり、型引数の置換によって 2 つのメソッドのシグネチャが同じになる場合にのみ有効である。
- オーバーライドされた基本メソッドは、仮想メソッド、抽象メソッド、またはオーバーライド メソッドである。 つまり、オーバーライドされた基本メソッドを静的または非仮想にすることはできない。
- オーバーライドされた基本メソッドは、シール メソッドではない。
- オーバーライドされた基本メソッドの戻り値の型とオーバーライド メソッドの間には同一型変換がある。
- オーバーライド宣言とオーバーライドされた基本メソッドには、同じ宣言されたアクセシビリティがある。 つまり、オーバーライド宣言では、仮想メソッドのアクセシビリティを変更できない。 ただし、オーバーライドされた基本メソッドが内部で保護されていて、オーバーライド宣言を含むアセンブリとは異なるアセンブリで宣言されている場合は、オーバーライド宣言の宣言されたアクセシビリティが保護される。
-
type_parameter_constraints_clauseは、継承された制約に従ってそれぞれ参照型または値型であることがわかっている
class
に適用されるstruct
primary_constraintのみで構成できます。T?
が型パラメーターである場合、オーバーライドメソッドのシグネチャにT
フォームの任意の型は、次のように解釈されます。- 型パラメーター
class
にT
制約が追加された場合、T?
は null 許容参照型になります。それ以外の場合 - 制約が追加されていない場合、または
struct
制約が追加された場合、型パラメーターT
T?
は null 許容値型になります。
- 型パラメーター
例: ジェネリック クラスに対してオーバーライドの規則がどのように機能するかを次に示しています。
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> }
終了サンプル
例: 型パラメーターが関係する場合のオーバーライドルールの動作を次に示します。
#nullable enable class A { public virtual void Foo<T>(T? value) where T : class { } public virtual void Foo<T>(T? value) where T : struct { } } class B: A { public override void Foo<T>(T? value) where T : class { } public override void Foo<T>(T? value) where T : struct { } }
型パラメーター制約
where T : class
しないと、参照型型パラメーターを持つ基本メソッドをオーバーライドできません。 終了サンプル
オーバーライド宣言は、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}"); } }
base.PrintFields()
でのB
の呼び出しは、A
で宣言された PrintFields メソッドを呼び出します。 base_access は、仮想呼び出しメカニズムを無効にし、基本メソッドを単に非virtual
メソッドとして扱います。B
の呼び出しが((A)this).PrintFields()
と記述されている場合、PrintFields
が仮想であり、B
の実行時の型がA
であるため、PrintFields
で宣言されたメソッドではなく、((A)this)
で宣言されたB
メソッドが再帰的に呼び出されます。終了サンプル
override
修飾子を含めた場合にのみ、メソッドは別のメソッドをオーバーライドできます。 それ以外のすべての場合、継承されたメソッドと同じシグネチャを持つメソッドは、単に継承されたメソッドを非表示にします。
例: 次のコード例の内容:
class A { public virtual void F() {} } class B : A { public virtual void F() {} // Warning, hiding inherited F() }
F
のB
メソッドにはoverride
修飾子が含まれていないため、F
のA
メソッドはオーバーライドされません。 代わりに、F
のB
メソッドはA
内のメソッドを非表示にし、宣言に new 修飾子が含まれていないため警告が報告されます。終了サンプル
例: 次のコード例の内容:
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 }
F
のB
メソッドは、F
から継承された仮想メソッドA
を非表示にします。F
の新しいB
にはプライベート アクセスがあるため、そのスコープにはB
のクラス本体のみが含まれており、C
には拡張されません。 したがって、F
でのC
の宣言は、F
から継承されたA
をオーバーライドできます。終了サンプル
15.6.6 シール メソッド
インスタンス メソッドの宣言に 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
には、F
修飾子を持つsealed
メソッドと、含まないG
メソッドの 2 つのオーバーライド メソッドが用意されています。B
のsealed
修飾子の使用により、C
はF
をさらにオーバーライドすることができなくなります。終了サンプル
15.6.7 抽象メソッド
インスタンス メソッドの宣言に 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
メソッドをオーバーライドして、実際の実装を提供する必要があります。終了サンプル
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()
呼び出しのコンパイル時エラーが報告されています。終了サンプル
抽象メソッド宣言は、仮想メソッドをオーバーライドできます。 これにより、抽象クラスは派生クラスでメソッドの再実装を強制して、メソッドの元の実装を使用できない状態にすることができます。
例: 次のコード例の内容:
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
は抽象メソッドをオーバーライドして独自の実装を提供します。終了サンプル
15.6.8 外部メソッド
メソッド宣言に extern
修飾子が含まれている場合、そのメソッドは外部メソッドと見なされます。 外部メソッドは外部で実装され、通常は 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); }
終了サンプル
15.6.9 部分メソッド
メソッド宣言に partial
修飾子が含まれている場合、そのメソッドは部分メソッドと見なされます。 部分メソッドは、partial 型 (§15.2.7) のメンバーとしてのみ宣言でき、さまざまな制限が適用されます。
部分メソッドは、型宣言の 1 つの部分で定義し、別の部分で実装できます。 実装は省略可能です。部分メソッドを実装する部分がない場合、部分メソッド宣言とそのすべての呼び出しは、部分の組み合わせから生成される型宣言から削除されます。
部分メソッドでは、アクセス修飾子を定義できません。これらは暗黙的にプライベートです。 戻り値の型は void
である必要があり、パラメーターは出力パラメーターにはできません。 識別子の partial
は、 キーワードの直前に出現する場合にのみ、メソッド宣言でコンテキスト キーワード (void
) として認識されます。 部分メソッドは、インターフェイス メソッドを明示的に実装できません。
部分メソッド宣言には 2 種類あります。メソッド宣言の本体がセミコロンの場合、その宣言は定義部分メソッド宣言と見なされます。 本体がセミコロン以外の場合、その宣言は実装部分メソッド宣言と見なされます。 型宣言の各部分で、特定のシグネチャを持つ定義部分メソッド宣言は 1 つだけであり、指定されたシグネチャを持つ実装部分メソッド宣言は、最大で 1 つだけである必要があります。 実装部分メソッド宣言が指定されている場合は、それに対応する定義部分メソッド宣言が存在し、宣言は次に示すように一致する必要があります。
- 宣言には、同じ修飾子 (必ずしも同じ順序とは限りません)、メソッド名、型パラメーターの数、およびパラメーターの数が含まれている必要があります。
- 宣言内の対応するパラメーターには、同じ修飾子 (必ずしも同じ順序ではない) と同じ型、または同一性変換可能な型 (型パラメーター名の剰余の違い) があります。
- 宣言内の対応する型パラメーターは、同じ制約 (型パラメーター名の剰余の違い) を持つ必要があります。
実装する部分メソッド宣言は、対応する定義部分メソッド宣言と同じ部分に含めることができます。
定義部分メソッドのみがオーバーロードの解決に参加します。 したがって、実装宣言が与えられているかどうかにかかわらず、呼び出し式は部分メソッドの呼び出しを解決する可能性があります。 部分メソッドは常に void
を返すため、このような呼び出し式は常に式ステートメントになります。 さらに、部分メソッドは暗黙的に private
であるため、このようなステートメントは常に、部分メソッドが宣言されている型宣言のいずれかの部分内で発生します。
注: 部分メソッド宣言の定義と実装に一致する定義では、パラメーター名が一致する必要はありません。 これは、名前付き引数 (§12.6.2.1) が使用される場合、意外にも、正しく定義された動作を引き起こす可能性があります。 たとえば、
M
の定義部分メソッド宣言が 1 つのファイルにあり、実装部分メソッド宣言が別のファイルにあるとします。// 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) {} }
上記の場合、呼び出しが、定義部分メソッド宣言ではなく、実装からの引数名を使用しているため、無効です。
注釈
部分型宣言に特定の部分メソッドの実装宣言が含まれていない場合、それを呼び出す式ステートメントは、結合された型宣言から単純に削除されます。 したがって、任意の部分式を含む呼び出し式は、実行時に作用しません。 部分メソッド自体も削除され、結合された型宣言のメンバーにはなりません。
特定の部分メソッドに実装宣言が存在する場合、部分メソッドの呼び出しは保持されます。 部分メソッドは、以下の点を除いて、実装部分メソッド宣言に類似したメソッド宣言を生成します
partial
修飾子は含まれません。結果のメソッド宣言の属性は、定義部分メソッド宣言と実装部分メソッド宣言の属性が未指定の順序で結合されたものです。 重複は削除されません。
結果のメソッド宣言のパラメーターの属性は、定義部分メソッド宣言と実装部分メソッド宣言の対応するパラメーターの属性が未指定の順序で結合されたものです。 重複は削除されません。
部分メソッド M
に対して定義宣言が指定されているが実装宣言が指定されていない場合は、次の制限が適用されます。
M
からデリゲートを作成 (§12.8.17.5) するのはコンパイル時エラーです。M
の呼び出しの一部として発生する式は、明確な代入の状態には影響を与えません (§9.4)。これは、コンパイル時エラーにつながる可能性があります。M
をアプリケーションのエントリ ポイントにすることはできません (§7.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 E拡張メソッド
メソッドの最初のパラメーターに this
修飾子が含まれている場合、そのメソッドは拡張メソッドと見なされます。 拡張メソッドは、非ジェネリックで入れ子になっていない静的クラスでのみ宣言する必要があります。 拡張メソッドの最初のパラメーターは、次のように制限されます。
- 値型を持つ場合にのみ、入力パラメーターにすることができます。
- 値型を持っているか、構造体に制約されているジェネリック型がある場合にのみ、参照パラメーターにすることができます。
- ポインター型としては使用できません。
例: 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; } }
終了サンプル
拡張メソッドは、通常の静的メソッドです。 さらに、外側の静的クラスがスコープ内にある場合は、拡張メソッドはインスタンス メソッド呼び出し構文 (§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)); } } }
終了サンプル
15.6.11 メソッド本体
メソッド宣言のメソッド本体は、ブロック本体、式本体、またはセミコロンのいずれかで構成されます。
抽象メソッド宣言と外部メソッド宣言ではメソッドの実装が提供されないため、メソッド本体は単にセミコロンで構成されます。 その他のメソッドの場合、メソッド本体はブロック (§13.3) であり、これにはそのメソッドが呼び出されたときに実行するステートメントが含まれます。
メソッドの有効な戻り値の型は、戻り値の型がvoid
場合、またはメソッドが非同期で戻り値の型がvoid
場合に«TaskType»
されます (§15.14.1)。 それ以外の場合、非非同期メソッドの有効な戻り値の型はその戻り値の型であり、戻り値の型が «TaskType»<T>
(§15.14.1) の非同期メソッドの有効な戻り値の型は T
。
メソッドの有効な戻り値の型が void
で、メソッドにブロック本体がある場合、ブロック内の return
ステートメント (§13.10.5) は式を指定してはなりません。 void メソッドのブロックの実行が正常に完了した場合 (つまり、メソッド本体の終わりで制御が外れる場合)、そのメソッドは単にその呼び出し元に戻ります。
メソッドの有効な戻り値の型が void
で、メソッドに式本体がある場合、式 E
は statement_expression でなければならず、本体は { E; }
の形式のブロック本体と完全に等しくなります。
値による戻り値メソッド (§15.6.1) の場合、そのメソッドの本体の各 return ステートメントでは、有効な戻り値の型に暗黙的に変換できる式を指定する必要があります。
値による戻り値メソッド (§15.6.1) の場合、そのメソッドの本体内の各 return ステートメントは、有効な戻り値の型であり、caller-context (§9.7.2) の ref-safe-context を持つ式を指定する必要があります。
値による戻り値メソッドと戻り値参照メソッドの場合、メソッド本体のエンドポイントは到達可能であってはなりません。 つまり、制御がメソッド本体の終端で外れることは許可されていません。
例: 次のコード例の内容:
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
メソッドは適切です。 本体が 1 つの return ステートメントを含むブロックに相当するため、I
メソッドは適切です。終了サンプル
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'
| 'readonly' // direct struct members only
| 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には、一連の属性 (§22) と、許可されている種類の宣言されたアクセシビリティのいずれか 1 つ (§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) を含めることができます。 さらに、struct_declaration によって直接含まれる property_declaration には、readonly
修飾子 (§16.4.11) を含めることができます。
- 1 つ目は、非参照値のプロパティを宣言します。 その値の型は type です。 この種類のプロパティは、読み取り可能または書き込み可能にできます。
- 2 つ目は、参照値プロパティを宣言します。 その値は、tvariable_reference (§9.5) で、
readonly
型の変数への にできます。 この種類のプロパティは読み取り専用です。
property_declarationには、一連の属性 (§22) と、許可されている種類の宣言されたアクセシビリティのいずれか 1 つ (§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 は単に識別子です。 明示的なインターフェイス メンバー実装 の場合 (§18.6.2)、member_name は、interface_type とその後に ".
" と識別子が続く形式です。
プロパティの型は、少なくともプロパティ自体と同程度にアクセス可能 (§7.5.5) である必要があります。
property_body は、ステートメント本体または式本体のいずれかで構成できます。 ステートメント本体では、accessor_declarations は "{
" トークンと "}
" トークンで囲まれ、プロパティのアクセサー (§15.7.3) を宣言します。 アクセサーは、プロパティの読み取りと書き込みに関連付けられている実行可能ステートメントを指定します。
property_body では、=>
に続いて式E
とセミコロンで構成される式本体は、ステートメント本体 { get { return E; } }
と完全に同等であり、したがって、get アクセサの結果が単一の式で与えられる読み取り専用プロパティを指定する場合にのみ使用できます。
property_initializer は、自動的に実装されるプロパティ (§15.7.4) に対してのみ指定でき、このようなプロパティの基になるフィールドを、式で指定された値で初期化します。
ref_property_body は、ステートメント本体または式本体のいずれかで構成できます。 ステートメント本体では、get_accessor_declaration はプロパティの get アクセサー (§15.7.3) を宣言します。 アクセサーは、プロパティの読み取りに関連付けられている実行可能ステートメントを指定します。
ref_property_body では、=>
とそれに続く ref
、variable_referenceV
、およびセミコロンからなる式本体は、ステートメント本体 { get { return ref V; } }
と完全に同等です。
注: プロパティにアクセスするための構文はフィールドの構文と同じですが、プロパティは変数として分類されません。 したがって、プロパティが参照値で、変数参照 (
in
) を返さない限り、プロパティをout
、ref
、または 引数として渡すことはできません。 注釈
プロパティ宣言に extern
修飾子が含まれている場合、そのプロパティは外部プロパティと見なされます。 外部プロパティ宣言は実際の実装を提供しないため、accessor_declarations 内の各 accessor_body はセミコロンにする必要があります。
15.7.2 静的プロパティとインスタンス プロパティ
プロパティ宣言に static
修飾子が含まれている場合、そのプロパティは静的プロパティと見なされます。
static
修飾子が存在しない場合、プロパティはインスタンス プロパティと見なされます。
静的プロパティは特定のインスタンスに関連付けられていないため、静的プロパティのアクセサーで this
を参照するのはコンパイル時エラーです。
インスタンス プロパティはクラスの特定のインスタンスに関連付けられます。そのインスタンスは、そのプロパティのアクセサーの this
(§12.8.14) としてアクセス可能です。
静的メンバーとインスタンス メンバーの違いについては、§15.3.8 で詳しく説明しています。
15.7.3 アクセサー
注: このサブ文書は、プロパティ (§15.7) とインデクサー (§15.9) の両方に適用されます。 この従属項では、プロパティの観点から記述されています。インデクサーについて読む際は、インデクサー (indexer/indexers) をプロパティ (property/properties) に置き換え、§15.9.2 で示されたプロパティとインデクサーの違いの一覧を参照してください。 注釈
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'
| 'readonly' // direct struct members only
;
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_get_accessor_declaration は、省略可能な属性、省略可能な accessor_modifier、get
トークン、その後に ref_accessor_body という形で構成されます。
accessor_modifier の使用は、次の制限で管理されています。
- accessor_modifier は、インターフェイスまたは明示的なインターフェイス メンバーの実装では使用できません。
-
accessor_modifier
readonly
は、struct_declaration (§16.4.11、§16.4.13) によって直接含まれるproperty_declarationまたはindexer_declarationでのみ許可されます。 -
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 は単にセミコロンです。 非抽象の非外部プロパティでも、インデクサーではない場合は、セミコロンで指定されたすべてのアクセサーの accessor_body を持つことができます。その場合は、自動実装プロパティ (§15.7.4) です。 自動的に実装されるプロパティには、少なくとも get アクセサーが必要です。 その他の非抽象の非外部プロパティのアクセサーの場合、accessor_body は次のいずれかになります。
- ブロック。対応するアクセサーが呼び出されたときに実行されるステートメントを指定します。
- 式本体。
=>
の後に式、セミコロンが続き、対応するアクセサーが呼び出されたときに実行される 1 つの式を表します。
abstract
および extern
参照値プロパティの場合、ref_accessor_body は単にセミコロンです。 その他の非抽象の非外部プロパティのアクセサーの場合、ref_accessor_body は次のいずれかになります。
- ブロック。get アクセサーが呼び出されたときに実行されるステートメントを指定します。
- 式本体。
=>
の後にref
、variable_reference、セミコロンが続く形式で構成されます。 変数参照は、get アクセサーが呼び出されたときに評価されます。
参照値以外のプロパティの get アクセサーは、プロパティ型の戻り値を持つパラメーターなしのメソッドに対応します。 代入のターゲットとして参照される場合を除き、このようなプロパティが式で参照されると、プロパティの値を計算するために get アクセサーが呼び出されます (§12.2.2)。
非参照値プロパティの get アクセサーの本体は、§15.6.11 で説明されている値を返すメソッドの規則に準拠する必要があります。 特に、get アクセサーの本体内のすべての return
ステートメントでは、プロパティ型に暗黙的に変換できる式を指定する必要があります。 さらに、get アクセサーの終端は到達可能であってはなりません。
参照値プロパティの get アクセサーは、プロパティ型の変数に対する variable_reference の戻り値を持つパラメーターなしのメソッドに対応します。 このようなプロパティが式で参照されると、プロパティの variable_reference 値を計算するために get アクセサーが呼び出されます。 その可変参照は、他の参照と同様に、コンテキストで必要に応じ参照された変数を読み取り、読み取り専用でない variable_reference の場合は、それを書き込みます。
例: 次の例は、代入のターゲットとしての参照値プロパティを示しています。
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 } }
終了サンプル
参照値プロパティの get アクセサーの本体は、§15.6.11 で説明されている参照値メソッドの規則に準拠する必要があります。
set アクセサーは、プロパティ型の単一の値パラメーターと void
戻り値の型を持つメソッドに対応します。 set アクセサーの暗黙的なパラメーターには、常に value
という名前が付けられます。 プロパティが代入のターゲット (§12.21) として、または ++
あるいは –-
のオペランド(§12.8.16、§12.9.6) として参照される場合は、新しい値を指定する引数と共に set アクセサーが呼び出されます (§12.21.2)。 set アクセサーの本体は、void
で説明されている メソッドの規則に準拠する必要があります。 特に、set アクセサー本体の return ステートメントでは、式を指定することはできません。 set アクセサーは暗黙的に value
という名前のパラメーターを持つため、set アクセサー内のローカル変数または定数宣言にその名前が付けられるとコンパイル時エラーになります。
get アクセサーと set アクセサーの有無に基づいて、プロパティは次のように分類されます。
- get アクセサーと set アクセサーの両方を含むプロパティは、読み取り/書き込みプロパティと見なされます。
- get アクセサーのみがあるプロパティは、読み取り専用プロパティと見なされます。 読み取り専用プロパティを代入のターゲットにすると、コンパイル時エラーになります。
- set アクセサーのみがあるプロパティは、書き込み専用プロパティと見なされます。 代入のターゲットとする場合を除き、式の書き込み専用プロパティを参照するとコンパイル時エラーになります。
注: プレフィックスとポストフィックスの
++
演算子と--
代入演算子、および複合代入演算子は、新しい値を書き込む前にオペランドの古い値を読み取るため、書き込み専用プロパティには適用できません。 注釈
例: 次のコード例の内容:
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 アクセサーは、プライベートstring
フィールドに格納されているcaption
を返します。 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 アクセサーは式のプロパティを参照することによって呼び出されます。
終了サンプル
プロパティの 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 つのメンバーは同じ名前を持つことができないため、この例ではコンパイル時エラーが発生します。
終了サンプル
派生クラスが継承プロパティと同じ名前でプロパティを宣言すると、派生プロパティは読み取りと書き込みの両方に関して継承されたプロパティを非表示にします。
例: 次のコード例の内容:
class A { public int P { set {...} } } class B : A { public new int P { get {...} } }
P
のB
プロパティは、読み取りと書き込みの両方に関して、P
のA
プロパティを非表示にします。 したがって、以下のステートメント内では:B b = new B(); b.P = 1; // Error, B.P is read-only ((A)b).P = 1; // Ok, reference to A.P
b.P
の読み取り専用P
プロパティでは、B
の書き込み専用のP
プロパティが非表示になるため、A
への代入によってコンパイル時エラーが報告されます。 ただし、キャストを使用すると、非表示のP
プロパティにアクセスできる点に留意してください。終了サンプル
パブリック フィールドとは異なり、プロパティはオブジェクトの内部状態とそのパブリック インターフェイスを分離します。
例:
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
クラスは、int
とx
の 2 つのy
フィールドを使用してその場所を格納します。 この場所は、X
とY
のプロパティとして公開されており、Location
型のPoint
プロパティとしても公開されています。 将来のバージョンの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
クラスにこのような変更を加えるのは不可能でした。終了サンプル
注: プロパティを使用して状態を公開することは、フィールドを直接公開するのに比べ、必ずしも効率が悪いわけではありません。 特に、プロパティが非仮想であり、コードの量が少ない場合、実行環境ではアクセサーの呼び出しがアクセサーの実際のコードに置き換えられる可能性があります。 このプロセスはインライン化と呼ばれ、フィールド アクセスと同じくらい効率的なプロパティ アクセスを実現しながら、プロパティの柔軟性も維持できます。 注釈
例: get アクセサーの呼び出しは概念的にはフィールドの値の読み取りと同等であるため、get アクセサーに観察可能な副作用が見られる場合は不適切なプログラミング スタイルと見なされます。 この例では
class Counter { private int next; public int Next => next++; }
Next
プロパティの値は、プロパティが以前にアクセスされた回数によって異なります。 したがって、プロパティにアクセスすると観察可能な副作用が生じるため、代わりにプロパティをメソッドとして実装する必要があります。get アクセサーの「副作用なし」の規則は、フィールドに格納されている値を単に返すように get アクセサーを常に記述する必要があるという意味ではありません。 実際、get アクセサーは、多くの場合、複数のフィールドにアクセスするかメソッドを呼び出すことによって、プロパティの値を計算します。 ただし、get アクセサーが適切に設計されていれば、オブジェクトの状態が観察可能な変更を引き起こすアクションは実行されません。
終了サンプル
プロパティは、リソースが最初に参照されるまでリソースの初期化を遅らせるために使用することができます。
例:
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
プロパティを参照しない場合は、それらのデバイスのオブジェクトは作成されません。終了サンプル
15.7.4 自動的に実装されるプロパティ
自動的に実装されるプロパティ (または短縮して自動プロパティ) は、セミコロンのみの accessor_body を持つ非抽象プロパティ、非外部プロパティ、非参照値プロパティです。 自動プロパティは 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; } } }
終了サンプル
例: 次の場合、
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; } }
読み取り専用フィールドへの割り当ては、コンストラクター内で行われるため有効です。
終了サンプル
バッキング フィールドは非表示ですが、そのフィールドには、自動的に実装されるプロパティの 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; } } }
終了サンプル
15.7.5 ユーザー補助
アクセサーに accessor_modifier がある場合、アクセサーのアクセシビリティ ドメイン (§7.5.3) は、accessor_modifier の宣言されたアクセシビリティを使用して決定されます。 アクセサーに accessor_modifier がない場合、アクセサーのアクセシビリティ ドメインは、プロパティまたはインデクサーの宣言されたアクセシビリティから決定されます。
accessor_modifier の有無は、メンバー検索 (§12.5) またはオーバーロード解決 (§12.6.4) には影響しません。 アクセスのコンテキストに関係なく、プロパティまたはインデクサーの修飾子が、どのプロパティまたはインデクサーにバインドされるかを常に決定します。
特定の非参照値のプロパティまたは非参照値のインデクサーが選択された場合、その使用が有効かどうかは、関与する特定のアクセシビリティ ドメインを使用して決定されます。
- 使用が値 (§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 } }
終了サンプル
特定の参照値プロパティまたは参照値インデクサーが選択されると (値として使用するか、単純な割り当てのターゲットとして使用するか、複合割り当てのターゲットとして使用するかに関係なく)、関連する 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 } }
終了サンプル
15.7.6 仮想アクセサー、シール アクセサー、オーバーライド アクセサー、および抽象アクセサー
注: このサブ文書は、プロパティ (§15.7) とインデクサー (§15.9) の両方に適用されます。 この従属項では、プロパティの観点から記述されています。インデクサーについて読む際は、インデクサー (indexer/indexers) をプロパティ (property/properties) に置き換え、§15.9.2 で示されたプロパティとインデクサーの違いの一覧を参照してください。 注釈
仮想プロパティ宣言は、プロパティのアクセサーが仮想であることを指定します。
virtual
修飾子は、プロパティのすべての非プライベート アクセサーに適用されます。 仮想プロパティのアクセサーに private
accessor_modifier がある場合、プライベート アクセサーは暗黙的に仮想ではありません。
抽象プロパティ宣言は、プロパティのアクセサーが仮想であることを指定しますが、アクセサーの実際の実装は提供しません。 代わりに、非抽象派生クラスは、プロパティをオーバーライドしてアクセサーに独自の実装を提供する必要があります。 抽象プロパティ宣言のアクセサーは実際の実装を提供しないため、その accessor_body は単にセミコロンで構成されます。 抽象プロパティには、private
アクセサーを含めてはなりません。
abstract
修飾子と override
修飾子の両方を含むプロパティ宣言は、プロパティが抽象プロパティであり、基本プロパティをオーバーライドすることを指定します。 このようなプロパティのアクセサーも抽象です。
抽象プロパティの宣言は、抽象クラスでのみ許可されます。(§15.2.2.2) 継承された仮想プロパティのアクセサーは、override
ディレクティブを指定するプロパティ宣言を含めることで、派生クラスでオーバーライドできます。 これは、 プロパティ宣言と呼ばれます。 オーバーライド プロパティ宣言は、新しいプロパティを宣言しません。 代わりに、既存の仮想プロパティのアクセサーの実装を単に特殊化します。
オーバーライド宣言とオーバーライドされた基本プロパティは、同じ宣言されたアクセシビリティを持つ必要があります。 つまり、オーバーライド宣言では、基本プロパティのアクセシビリティは変更されません。 ただし、オーバーライドされた基本プロパティが内部で保護されていて、オーバーライド宣言を含むアセンブリとは異なるアセンブリで宣言されている場合は、オーバーライド宣言の宣言されたアクセシビリティが保護されます。 継承されたプロパティに 1 つのアクセサーしかない場合 (つまり、継承されたプロパティが読み取り専用または書き込み専用である場合)、オーバーライドするプロパティはそのアクセサーのみを含める必要があります。 継承されたプロパティに両方のアクセサーが含まれている場合 (つまり、継承されたプロパティが読み書き可能な場合)、オーバーライドするプロパティにはどちらか一方または両方のアクセサーを含めることができます。 オーバーライドするプロパティと継承されたプロパティの型の間には同一型変換が必要です。
オーバーライド プロパティ宣言には、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
の宣言は両方の抽象アクセサーをオーバーライドします。したがって、abstract
には未処理のB
関数メンバーはなく、B
は非抽象クラスとして許可されます。終了サンプル
プロパティがオーバーライドとして宣言されている場合、オーバーライドされたアクセサーは、オーバーライドするコードからアクセス可能である必要があります。 さらに、プロパティまたはインデクサー自体とアクセサーの両方の宣言されたアクセシビリティは、オーバーライドされたメンバーとアクセサーのアクセシビリティと一致する必要があります。
例:
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 } }
終了サンプル
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'
| 'readonly' // direct struct members only
| 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) でのみ使用できます。
event_declaration には、一連の属性 (§22) と、許可されている種類の宣言されたアクセシビリティのいずれか (§15.3.6)、および 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)、extern
(§15.6.8) 修飾子を含めることができます。
event_declaration が struct_declaration によって直接含まれる場合、readonly
修飾子 (§16.4.12) を含むことができます。
イベント宣言は、修飾子の有効な組み合わせに関して、メソッド宣言 (§15.6) と同じ規則に従います。
イベント宣言の 型 は、delegate_type (§8.2.8) で、delegate_type はイベント自体 (§7.5.5) と同じかそれ以上にアクセス可能である必要があります。
イベント宣言には、event_accessor_declaration を含めることができます。 ただし、含まれていなければ、非 extern 非抽象イベントの場合、コンパイラによって自動的に提供されます (§15.8.2)。extern
イベントの場合、アクセサーは外部から提供されます。
event_accessor_declaration を省略するイベント宣言では、variable_declarator ごとに 1 つ以上のイベントが定義されます。 属性と修飾子は、このような event_declaration によって宣言されたすべてのメンバーに適用されます。
event_declaration が abstract
修飾子と event_accessor_declaration を両方含むことは、コンパイル時エラーです。
イベント宣言に extern
修飾子が含まれている場合、そのイベントは外部イベントと見なされます。 外部イベント宣言は実際の実装を提供しないため、extern
修飾子と event_accessor_declaration の両方を含めるとエラーになります。
または abstract
修飾子を持つイベント宣言の external
に variable_initializer を含むことは、コンパイル時エラーです。
イベントは、+=
演算子および -=
演算子の左オペランドとして使用できます。 これらの演算子はそれぞれ、イベント ハンドラーをアタッチしたり、イベントからイベント ハンドラーを削除したりするために使用され、イベントのアクセス修飾子は、そのような操作が許可されるコンテキストを制御します。
イベントが宣言されている型の外部にあるコードによってイベントに対して許可される演算は、+=
と -=
だけです。 そのため、このようなコードではイベントのハンドラーを追加および削除できますが、イベント ハンドラーの基になるリストを直接取得または変更することはできません。
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
イベントにアタッチされます。終了サンプル
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
クラス内のフィールドとして使用されます。 この例で示すように、フィールドは、デリゲート呼び出し式で検査、変更、および使用できます。OnClick
クラスのButton
メソッドは、Click
イベントを「発生」させます。 イベントを発生させるという概念は、イベントによって表されるデリゲートの呼び出しとまったく同じです。したがって、イベントを発生させるための特殊な言語コンストラクトはありません。 デリゲート呼び出しの前には、デリゲートが null ではないことを確認するチェックが行われ、スレッドセーフを確保するためにローカル コピーでチェックされる点に留意してください。
Button
クラスの宣言の外部では、Click
メンバーは、次のように、+=
演算子と–=
演算子の左側でのみ使用できます。b.Click += new EventHandler(...);
これにより、
Click
イベントの呼び出しリストにデリゲートが追加されます。Click –= new EventHandler(...);
これにより、
Click
イベントの呼び出しリストからデリゲートが削除されます。終了サンプル
フィールドに似たイベントをコンパイルする場合、コンパイラはデリゲートを保持するストレージを自動的に作成し、デリゲート フィールドにイベント ハンドラーを追加または削除するイベントのアクセサーを作成します。 追加と削除の演算はスレッド セーフであり、インスタンス イベントの包含オブジェクトのロック (§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
」という名前は任意です。非表示フィールドには任意の名前を付けるか、名前を付けないことも可能です。注釈
15.8.3 イベント アクセサー
注: イベント宣言では、上記の の例のように、通常、
Button
は省略されます。 たとえば、イベントごとに 1 つのフィールドのストレージ コストが許容されない場合、これらは含まれることがあります。 このような場合、クラスは event_accessor_declaration を含めることができ、イベント ハンドラーの一覧を格納するためのプライベート メカニズムを使用できます。 注釈
イベントの event_accessor_declarations は、イベント ハンドラーの追加と削除に関連付けられている実行可能ステートメントを指定します。
アクセサー宣言は、add_accessor_declaration と remove_accessor_declarationで構成されます。 各アクセサー宣言は、add または remove トークン後に block が続く形で構成されます。 add_accessor_declaration に関連付けられているブロックは、イベント ハンドラーが追加されたときに実行するステートメントを指定し、remove_accessor_declaration に関連付けられているブロックは、イベント ハンドラーが削除されたときに実行するステートメントを指定します。
各 add_accessor_declaration と remove_accessor_declaration は、イベント型の単一の値パラメーターと void
の戻り値の型を持つメソッドに対応します。 イベント アクセサーの暗黙的なパラメーターには、value
という名前が付けられます。 イベントの代入でイベントを使用する場合は、適切なイベント アクセサーが使用されます。 具体的には、代入演算子が +=
の場合は add アクセサーが使用され、代入演算子が –=
の場合は remove アクセサーが使用されます。 どちらの場合も、代入演算子の右オペランドがイベント アクセサーの引数として使用されます。
add_accessor_declaration または remove_accessor_declaration のブロックは、void
で説明されている メソッドの規則に準拠します。 特に、このようなブロック内の return
ステートメントでは、式を指定することはできません。
event アクセサーは暗黙的に value
という名前のパラメーターを持つため、event アクセサー内の宣言されたローカル変数または定数にその名前が付けられるとコンパイル時エラーになります。
例: 次のコード例の内容:
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 デリゲート値をキーに関連付けるコストが発生しないように設計されており、したがって未処理のイベントがストレージを消費しません。終了サンプル
15.8.4 静的イベントとインスタンス イベント
イベント宣言に static
修飾子が含まれている場合、そのイベントは静的イベントと見なされます。
static
修飾子が存在しない場合、イベントはインスタンス イベントと見なされます。
静的イベントは特定のインスタンスに関連付けられていないため、静的イベントのアクセサーで this
を参照するのはコンパイル時エラーです。
インスタンス イベントはクラスの特定のインスタンスに関連付けられます。このインスタンスは、そのイベントのアクセサーの this
(§12.8.14) としてアクセス可能です。
静的メンバーとインスタンス メンバーの違いについては、§15.3.8 で詳しく説明しています。
15.8.5 仮想アクセサー、シール アクセサー、オーバーライド アクセサー、および抽象アクセサー
仮想イベント宣言は、そのイベントのアクセサーが仮想であることを指定します。
virtual
修飾子は、イベントの両方のアクセサーに適用されます。
抽象イベント宣言は、イベントのアクセサーが仮想であることを指定しますが、アクセサーの実際の実装は提供しません。 代わりに、非抽象派生クラスは、イベントをオーバーライドしてアクセサーに独自の実装を提供する必要があります。 抽象イベント宣言のアクセサーは実際の実装を提供しないため、event_accessor_declaration を提供しません。
abstract
修飾子と override
修飾子の両方を含むイベント宣言は、イベントが抽象イベントであり、基本イベントをオーバーライドすることを指定します。 このようなイベントのアクセサーも抽象です。
抽象イベントの宣言は、抽象クラスでのみ許可されます。(§15.2.2.2)
継承された仮想イベントのアクセサーは、override
修飾子を指定するイベント宣言を含めることで、派生クラスでオーバーライドできます。 これは、 イベント宣言と呼ばれます。 オーバーライド イベント宣言は、新しいイベントを宣言しません。 代わりに、既存の仮想イベントのアクセサーの実装を単に特殊化します。
オーバーライド イベント宣言では、オーバーライドされたイベントとまったく同じアクセシビリティ修飾子と名前を指定し、オーバーライドするイベントの型とオーバーライドされたイベントの型の間で同一型変換を行い、add アクセサーと remove アクセサーの両方を宣言内で指定する必要があります。
オーバーライド イベント宣言には、sealed
修飾子を含めることができます。
this
修飾子を使用すると、派生クラスがイベントをさらにオーバーライドするのを防ぐことができます。 シール イベントのアクセサーもシールされます。
オーバーライドするイベント宣言に new
修飾子を含めることは、コンパイル時エラーです。
宣言と呼び出し構文の違いを除き、仮想、シール、オーバーライド、および抽象アクセサーは、仮想、シール、オーバーライド、および抽象メソッドとまったく同じように動作します。 具体的には、§15.6.4、§15.6.5、§15.6.6、および §15.6.7 で説明されている規則は、 アクセサーが対応する形式のメソッドであるかのように適用されます。 各アクセサーは、イベント型の単一の値パラメーター、void
の戻り値の型、および包含イベントと同じ修飾子を持つメソッドに対応します。
15.9 インデクサー
15.9.1 全般
インデクサーは、配列と同じ方法でオブジェクトのインデックスを作成できるようにするメンバーです。 インデクサーは、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'
| 'readonly' // direct struct members only
| 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 には、属性 (§22) と、許可されている種類の宣言されたアクセシビリティ (§15.3.6)、およびnew
(§15.3.5)、virtual
(§15.6.4)、override
(§15.6.5)、sealed
(§15.6.6)、abstract
(§15.6.7)、extern
(§15.6.8) の修飾子を含めることができます。 さらに struct_declaration で直接含まれる indexer_declaration には、readonly
修飾子 (§16.4.12) を含めることができます。
- 1 つ目は、非参照値のインデクサーを宣言します。 その値の型は type です。 この種類のインデクサーは、読み取り可能または書き込み可能にできます。
- 2 つ目は、参照値インデクサーを宣言します。 その値は、tvariable_reference (§9.5) で、
readonly
型の変数への にできます。 この種類のインデクサーは読み取り専用です。
indexer_declaration には、一連のattributes (§22) と、許可されている種類の宣言されたアクセシビリティのいずれか (§15.3.6)、new
(§15.3.5)、virtual
(§15.6.4)、override
(§15.6.5)、sealed
(§15.6.6)、abstract
(§15.6.7)、および extern
(§15.6.8) 修飾子を含めることができます。
インデクサー宣言は、修飾子の有効な組み合わせに関してメソッド宣言 (§15.6) と同じ規則に従いますが、static
修飾子がインデクサー宣言では許可されないという 1 つの例外があります。
インデクサー宣言の型は、宣言によって導入されたインデクサーの要素型を指定します。
注: インデクサーは配列要素に似たコンテキストで使用されるように設計されているため、配列に対して定義されている要素型という用語もインデクサーでも使用されます。 注釈
インデクサーが明示的なインターフェイス メンバー実装でない限り、type の後にキーワード this
が続きます。 明示的なインターフェイス メンバー実装の場合は、type の後に、interface_type、".
"、およびキーワード this
が続きます。 他のメンバーとは異なり、インデクサーにはユーザー定義の名前がありません。
parameter_list は、インデクサーのパラメーターを指定します。 インデクサーのパラメーター リストは、メソッドのパラメーター リスト (§15.6.2) に対応します。ただし、少なくとも 1 つのパラメーターを指定する必要があり、this
、ref
、および out
パラメーター修飾子は許可されません。
インデクサーの型と、parameter_list で参照される各型は、インデクサー自体と同じかそれ以上のアクセスが可能である必要があります (§7.5.5)。
indexer_body は、ステートメント本体 (§15.7.1) または式本体 (§15.6.1) のいずれかで構成されます。 ステートメント本体では、accessor_declarations は "{
" トークンと "}
" トークンで囲まれ、インデクサーのアクセサー (§15.7.3) を宣言します。 アクセサーは、インデクサー要素の読み取りと書き込みに関連付けられている実行可能ステートメントを指定します。
property_body では、「=>
」に続いて式 E
とセミコロンで構成される式本体は、ステートメント本体 { get { return E; } }
と完全に同等であり、したがって、get アクセサの結果が単一の式で与えられる読み取り専用インデクサーを指定する場合にのみ使用できます。
ref_indexer_body は、ステートメント本体または式本体のいずれかで構成できます。 ステートメント本体では、get_accessor_declaration はインデクサーの get アクセサー (§15.7.3) を宣言します。 アクセサーは、インデクサーの読み取りに関連付けられている実行可能ステートメントを指定します。
ref_indexer_body では、=>
とそれに続く ref
、variable_referenceV
、およびセミコロンからなる式本体は、ステートメント本体 { get { return ref V; } }
と完全に同等です。
注: インデクサー要素にアクセスするための構文は配列要素の構文と同じですが、インデクサー要素は変数として分類されません。 したがって、インデクサーが参照値で、変数参照 (
in
) を返さない限り、インデクサー要素をout
、ref
、または 引数として渡すことはできません。 注釈
インデクサーの 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 ビットしか占めないのに対し、後者は 1byte
を占めるため)、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; } } }
終了サンプル
15.9.2 インデクサーとプロパティの違い
インデクサーとプロパティの概念は非常に似ていますが、次の点で異なります。
- プロパティは名前で識別されますが、インデクサーはシグネチャによって識別されます。
- プロパティには、simple_name (§12.8.4) または member_access (§12.8.7) を介してアクセスします。一方、インデクサー要素には element_access (§12.8.12.3) を介してアクセスします。
- プロパティは静的メンバーにすることができますが、インデクサーは常にインスタンス メンバーです。
- プロパティの get アクセサーはパラメーターのないメソッドに対応しますが、インデクサーの get アクセサーはインデクサーと同じパラメーター リストを持つメソッドに対応します。
- プロパティの set アクセサーは、
value
という名前の 1 つのパラメーターを持つメソッドに対応します。一方、インデクサーの set アクセサーは、インデクサーと同じパラメーター リストと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 を読む際のプロパティ (property/properties) のインデクサー (indexer/indexers) への置き換えは、定義された用語にも適用されます。 具体的には、読み取り/書き込みプロパティは読み取り/書き込みインデクサーに、読み取り専用プロパティは読み取り専用インデクサーに、書き込み専用プロパティは書き込み専用インデクサーになります。
15.10 オペレーター
15.10.1 全般
演算子は、クラスのインスタンスに適用できる式演算子の意味を定義するメンバーです。 演算子は、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) は、同じ語彙トークン (!
) で表されますが、異なるものです。 後者はオーバーロード可能な演算子ではありません。
注釈
オーバーロード可能な演算子には、単項演算子 (§15.10.2)、二項演算子 (§15.10.3)、および変換演算子 (§15.10.4) の 3 つのカテゴリがあります。
operator_body は、セミコロン、ブロック本文 (§15.6.1) または式本体 (§15.6.1) のいずれかです。 ブロック本体は、演算子の呼び出し時に実行するステートメントを指定するブロックで構成されます。
ブロックは、§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 } }
演算子メソッドが、オペランドに 1 を追加して生成された値を返す方法に注意してください。これは、後置インクリメント演算子とデクリメント演算子 (§12.8.16) と、前置インクリメント演算子とデクリメント演算子 (§12.9.6) と同様です。 C++ とは異なり、このメソッドではオペランドの値を直接変更してはなりません。これは、後置インクリメント演算子 (§12.8.16) の標準セマンティクスに違反します。
終了サンプル
15.10.3 二項演算子
次の規則は二項演算子宣言に適用されます。ここで、T
は、演算子宣言を含むクラスまたは構造体のインスタンス型を表します。
- 二項非シフト演算子は、2 つのパラメーターを受け取る必要があります。少なくとも 1 つは
T
またはT?
の型を持ち、任意の型を返すことができます。 - バイナリ
<<
または>>
演算子 (§12.11)は 2 つのパラメータを取るものとし、1 つ目はT
型またはT?
型、2つ目はint
型またはint?
型とし、任意の型を返すことができます。
二項演算子のシグネチャは、演算子トークン (+
、-
、*
、/
、%
、&
、|
、^
、<<
、>>
、==
、!=
、>
、<
、>=
、または <=
) と 2 つのパラメーターの型で構成されます。 戻り値の型とパラメーターの名前は、二項演算子のシグネチャの一部ではありません。
特定の二項演算子では、ペアでの宣言が必要です。 ペアを成す二項演算子のいずれか一方の宣言がある場合、そのペアのもう一方の演算子についても対応する宣言が必要です。 2 つの演算子宣言が一致するとは、その戻り値の型と対応するパラメータの型の間で同一型変換が存在する場合をいいます。 次の演算子には、ペアでの宣言が必要です。
- 演算子
==
と演算子!=
- 演算子
>
と演算子<
- 演算子
>=
と演算子<=
15.10.4 変換演算子
変換演算子宣言では、ユーザー定義の変換 (§10.5) が導入され、事前に定義された暗黙的および明示的な変換が強化されます。
implicit
キーワードを含む変換演算子宣言では、ユーザー定義の暗黙的な変換が導入されます。 暗黙的な変換は、関数メンバーの呼び出し、キャスト式、代入など、さまざまな状況で発生する可能性があります。 これについては、§12.21 で詳しく説明しています。
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
がそれぞれ関係のない一意の型と見なされるため、許可されます。 ただし、3 番目の演算子は、C<T>
がD<T>
の基底クラスであるためエラーです。終了サンプル
2 番目の規則から、変換演算子は、演算子が宣言されているクラスまたは構造体型への変換、またはその逆方向の変換を行う必要があります。
例: クラスまたは構造体の型
C
で、C
からint
への変換とint
からC
への変換を定義できますが、int
からbool
への変換は定義できません。 終了サンプル
定義済みの変換を直接再定義することはできません。 したがって、変換演算子は、object
とその他のすべての型の間に暗黙的な変換と明示的な変換が既に存在するため、object
との間での変換が許可されていません。 同様に、変換元と変換先のどちらの型も、もう一方の基本型にすることはできません。これは変換が既に存在するためです。 ただし、特定の型引数に対して、定義済みの変換として既に存在する変換を指定するジェネリック型に対して、演算子を宣言することはできます。
例:
struct Convertible<T> { public static implicit operator Convertible<T>(T value) {...} public static explicit operator T(Convertible<T> value) {...} }
object
型がT
の型引数として指定されている場合、2 番目の演算子は既に存在する変換を宣言します (暗黙的であり、したがって、任意の型からオブジェクト型への明示的な変換が存在します)。終了サンプル
2 つの型間に事前に定義された変換が存在する場合、それらの型間のユーザー定義変換は無視されます。 具体的な内容は次のとおりです。
- 型 から型
S
への定義済みの暗黙的な変換 (T
) が存在する場合、S
からT
へのすべてのユーザー定義変換 (暗黙的または明示的) は無視されます。 - 型 から型
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 }
終了サンプル
ユーザー定義の変換は、interface_type から変換することも、interface_type に変換することもできません。 特に、この制限により、interface_type 型への変換時にユーザー定義の変換が発生しないことが保証され、interface_type 型への変換は、変換対象の object
が指定された interface_type を実際に実装している場合にのみ成功します。
変換演算子のシグネチャは、ソース型とターゲット型で構成されます。 (これは、戻り値の型がシグネチャに参加するメンバーの唯一の形式です。) 変換演算子の暗黙的または明示的な分類は、演算子のシグネチャの一部ではありません。 したがって、クラスまたは構造体は、同じソース型とターゲット型を持つ暗黙的な変換演算子と明示的な変換演算子の両方を宣言することはできません。
注: 一般に、ユーザー定義の暗黙的な変換は、例外をスローし、情報を失わないよう設計する必要があります。 ユーザー定義の変換によって例外が発生する可能性がある場合 (たとえば、ソース引数が範囲外である場合)、または情報の損失 (上位ビットの破棄など) が発生する可能性がある場合は、その変換を明示的な変換として定義する必要があります。 注釈
例: 次のコード例の内容:
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
の可能な値のサブセットしか表現できないため明示的です。終了サンプル
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 は、一連の属性 (§22) と、許可されている宣言されたアクセシビリティのいずれか (§15.3.6)、および extern
(§15.6.8) 修飾子を含むことができます。 コンストラクター宣言は、同じ修飾子を複数回含めることはできません。
constructor_declarator の識別子は、インスタンス コンストラクターが宣言されているクラスの名前を指定する必要があります。 他の名前が指定された場合は、コンパイル時エラーが発生します。
インスタンス コンストラクターの省略可能な 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 は次のいずれかで構成されます。
- ブロック。クラスの新しいインスタンスを初期化するステートメントを指定します。または
- 式本体。
=>
の後に式とセミコロンが続く形で構成され、クラスの新しいインスタンスを初期化する単一の式を表します。
ブロックまたは式本体である 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 に示されているオーバーロード解決規則を使用して選択されます。 候補となるインスタンス コンストラクタのセットは、直接の基底クラスのアクセス可能なすべてのインスタンス コンストラクタから構成されます。 このセットが空の場合、または最適なインスタンス コンストラクタを特定できない場合は、コンパイル時エラーが発生します。 -
this(
(argument_list))
(ここで argument_list は省略可能) の形式のインスタンス コンストラクタの初期化子は、同じクラス内の別のインスタンス コンストラクタを呼び出します。 コンストラクタは、argument_list と §12.6.4 に示されているオーバーロード解決規則を使用して選択されます。 候補となるインスタンス コンストラクタのセットは、そのクラス自体内の宣言されたすべてのインスタンス コンストラクタから構成されます。 結果の適用可能なインスタンス コンストラクタのセットが空の場合、または単一の最適なインスタンス コンストラクタを特定できない場合、コンパイル時エラーが発生します。 インスタンス コンストラクター宣言が 1 つ以上の一連のコンストラクター初期化子を介して自身を呼び出すと、コンパイル時エラーが発生します。
インスタンス コンストラクターにコンストラクター初期化子がない場合は、base()
形式のコンストラクター初期化子が暗黙的に提供されます。
注: したがって、次のようなインスタンス コンストラクター宣言では
C(...) {...}
これは、次と完全に同等です。
C(...) : base() {...}
注釈
インスタンス コンストラクター宣言の parameter_list によって指定されるパラメーターの範囲には、その宣言のコンストラクター初期化子が含まれます。 したがって、コンストラクター初期化子は、コンストラクターのパラメーターにアクセスできます。
例:
class A { public A(int x, int y) {} } class B: A { public B(int x, int y) : base(x + y, x - y) {} }
終了サンプル
インスタンス コンストラクター初期化子は、作成中のインスタンスにアクセスできません。 したがって、コンストラクター初期化子の引数式でこれを参照するのはコンパイル時エラーです。これは、引数式が simple_name を介してインスタンス メンバーを参照することがコンパイル時エラーになるためです。
15.11.3 インスタンス変数初期化子
非外部インスタンス コンストラクターにコンストラクター初期化子がない場合、または形式が base(...)
のコンストラクター初期化子がある場合、そのコンストラクターは、クラスで宣言されたインスタンス フィールドの variable_initializer で指定された初期化を暗黙的に実行します。 これは、コンストラクターへのエントリの直後、および直接基底クラス コンストラクターの暗黙的な呼び出しの前に実行される、一連の代入に対応します。 変数初期化子は、クラス宣言 (§15.5.6.1) に示されるテキストの順序で実行されます。
変数初期化子は、外部インスタンス コンストラクターによって実行される必要はありません。
15.11.4 コンストラクターの実行
変数初期化子は代入ステートメントに変換され、これらの代入ステートメントは基底クラス インスタンス コンストラクターの呼び出し前に実行されます。 この順序により、そのインスタンスにアクセスできる任意のステートメントが実行される前に、すべてのインスタンス フィールドが変数初期化子によって初期化されます。
例: 以下のコードでは
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; } }
終了サンプル
15.11.5 デフォルト コンストラクタ
クラスにインスタンス コンストラクター宣言が含まれている場合、既定のインスタンス コンストラクターが自動的に提供されます。 その既定のコンストラクターは、base()
形式のコンストラクター初期化子があるかのように、直接基底クラスのコンストラクターを単に呼び出します。 クラスが抽象クラスの場合、既定のコンストラクターの宣言されたアクセシビリティが保護されます。 それ以外の場合、既定のコンストラクターに対して宣言されたアクセシビリティはパブリックです。
注: したがって、既定のコンストラクターは常に次の形式になります。
protected C(): base() {}
または
public C(): base() {}
ここで
C
はクラスの名前です。注釈
オーバーロードの解決で、基底クラス コンストラクター初期化子の一意の最適な候補を特定できない場合は、コンパイル時エラーが発生します。
例: 次のコード例の内容:
class Message { object sender; string text; }
クラスにインスタンス コンストラクター宣言がないため、既定のコンストラクターが提供されます。 したがって、この例は次と完全に等しくなります。
class Message { object sender; string text; public Message() : base() {} }
終了サンプル
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 には、属性 (§22) のセットと 1 つのextern
修飾子 (§15.6.8) を含めることができます。
static_constructor_declaration の識別子は、静的コンストラクターが宣言されているクラスの名前を指定する必要があります。 他の名前が指定された場合は、コンパイル時エラーが発生します。
静的コンストラクター宣言に extern
修飾子が含まれている場合、その静的コンストラクターは外部静的コンストラクターと見なされます。 外部静的コンストラクター宣言は実際の実装を提供しないため、その static_constructor_body はセミコロンで構成されます。 その他のすべての静的コンストラクター宣言の場合、static_constructor_body は次のいずれかで構成されます。
- ブロック。クラスを初期化するために実行するステートメントを指定します。または
- 式本体。
=>
の後に式とセミコロンが続く形で構成され、クラスを初期化するために実行する単一の式を表します。
ブロックまたは式本体である static_constructor_body は、 の戻り値の型 (void
) を持つ静的メソッドの 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
の呼び出しによってトリガーされるためです。終了サンプル
変数初期化子を持つ静的フィールドを既定値の状態で観察できるようにする循環依存関係を構築することが可能です。
例: 例
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.Y
の静的コンストラクターの前に、最初にB
の初期化子を実行します。Y
の初期化子により、A
のstatic
コンストラクターが実行されます。これは、A.X
の値が参照されるためです。A
の静的コンストラクターは、X
の値の計算に進み、その際にY
の既定値 (ゼロ) を取得します。 したがってA.X
は、1 に初期化されます。A
の静的フィールド初期化子と静的コンストラクターを実行するプロセスが完了して、Y
の初期値の計算に戻り、その結果は 2 になります。終了サンプル
静的コンストラクターは、構築されたクローズ クラス型ごとに 1 回だけ実行されるため、コンパイル時に制約 (§15.2.5) でチェックできない型パラメータのランタイム チェックを強制するのに便利です。
例: 次の型は、静的コンストラクターを使用して、型引数が列挙型であることを強制します。
class Gen<T> where T : struct { static Gen() { if (!typeof(T).IsEnum) { throw new ArgumentException("T must be an enum"); } } }
終了サンプル
15.13 ファイナライザー
注: この仕様の以前のバージョンでは、現在「ファイナライザー」と呼ばれているものが「デストラクター」と呼ばれていました。 経験上、「デストラクター」という用語は混乱を引き起こし、多くの場合、特に C++ を知っているプログラマにとって、誤った期待を生じさせました。 C++ ではデストラクターは確定的な方法で呼び出されますが、C# の場合、ファイナライザーはそのように呼び出されません。 C# から確定的な動作を取得するには、
Dispose
を使用する必要があります。 注釈
ファイナライザーは、クラスのインスタンスを最終処理するために必要なアクションを実装するメンバーです。 ファイナライザーは、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 には、属性 (§22) のセットを含めることができます。
finalizer_declarator の識別子は、ファイナライザーが宣言されているクラスの名前を指定する必要があります。 他の名前が指定された場合は、コンパイル時エラーが発生します。
ファイナライザー宣言に extern
修飾子が含まれている場合、ファイナライザーは外部ファイナライザーと見なされます。 外部ファイナライザー宣言は実際の実装を提供しないため、その finalizer_body はセミコロンで構成されます。 その他すべてのファイナライザーでは、finalizer_body は次のいずれかで構成されます。
- ブロック。クラスのインスタンスの最終処理を行うために実行するステートメントを指定します。
- 式本体。
=>
の後に式とセミコロンが続く形で構成され、クラスのインスタンスの最終処理をするために実行する単一の式を表します。
ブロックまたは式本体であるfinalizer_body は、 の戻り値の型 (void
) を持つインスタンス メソッドの method_bodyに完全に対応します。
ファイナライザーは継承されません。 したがって、クラスには、そのクラスで宣言できるファイナライザー以外のファイナライザーは存在しません。
注: ファイナライザーはパラメーターを持たないようにする必要があり、オーバーロードできません。したがって、クラスが持つことのできるファイナライザーは最大で 1 つです。 注釈
ファイナライザーは自動的に呼び出され、明示的に呼び出すことはできません。 インスタンスがどのコードでも使用できなくなると、そのインスタンスは最終処理の対象になります。 インスタンスのファイナライザーの実行は、インスタンスが最終処理の対象になるといつでも実行される可能性があります (§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
これは、継承チェーン内のファイナライザーは、最も派生したものから最も派生が少ないものの順に呼び出されるためです。
終了サンプル
ファイナライザーは、Finalize
で仮想メソッド System.Object
をオーバーライドすることによって実装されます。 C# プログラムでは、このメソッドをオーバーライドしたり、メソッド (またはそのオーバーライド) を直接呼び出したりすることはできません。
例: 次のプログラムの場合
class A { override protected void Finalize() {} // Error public void F() { this.Finalize(); // Error } }
2 つのエラーがあります。
終了サンプル
コンパイラは、このメソッドとそのオーバーライドが存在しないかのように動作します。
例: したがって、次のプログラムは
class A { void Finalize() {} // Permitted }
有効であり、表示されているメソッドは
System.Object
のFinalize
メソッドを非表示にしています。終了サンプル
ファイナライザーから例外がスローされたときの動作については、§21.4 を参照してください。
15.14 非同期関数
15.14.1 全般
修飾子を持つメソッド (§15.6) または匿名関数 (async
) は、非同期関数と呼ばれます。 一般に、非同期という用語は、async
修飾子を持つ任意の種類の関数を記述するために使用されます。
非同期関数のパラメーター リストが、任意の in
、out
、または ref
パラメーター、または ref struct
型のパラメーターを指定するのはコンパイル時エラーです。
非同期メソッドの return_type は、 void
、 タスク型、または 非同期反復子型 (§15.15) のいずれかになります。 結果値を生成する非同期メソッドの場合、タスク型または非同期反復子型 (§15.15.3) はジェネリックである必要があります。 結果値を生成しない非同期メソッドの場合、タスク型はジェネリックではありません。 このような型は、本仕様書では、それぞれ «TaskType»<T>
および «TaskType»
として参照されます。
System.Threading.Tasks.Task
およびSystem.Threading.Tasks.Task<TResult>
から構築される標準ライブラリの種類System.Threading.Tasks.ValueTask<T>
と型は、タスクの種類だけでなく、属性を介してに関連付けられているクラス、構造体、またはインターフェイス型です。 このような型は、本仕様書では、«TaskBuilderType»<T>
および «TaskBuilderType»
として参照されます。 タスク型は、最大で 1 つの型パラメーターを持ち、ジェネリック型で入れ子にすることはできません。
タスク型を返す非同期メソッドは、タスク戻りと見なされます。
タスクの型は正確な定義によって異なりますが、言語の観点からは、タスク型は、未完了、成功、または失敗のいずれかのステータスになります。
失敗のタスクは、関連する例外を記録します。
成功«TaskType»<T>
の場合は、型 T
の結果を記録します。 タスク型は待機可能であるため、タスクは await 式のオペランドにすることができます (§12.9.8)。
例: 以下では、タスク型
MyTask<T>
が、タスク ビルダー型MyTaskMethodBuilder<T>
と待機型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() { ... } }
終了サンプル
タスク ビルダー型は、特定のタスクの種類 (§15.14.2) に対応するクラスまたは構造体型です。 タスク ビルダー型は、対応するタスクの型の宣言されたアクセシビリティと完全に一致する必要があります。
注: タスク型が
internal
で宣言されている場合は、対応するビルダー型もinternal
で宣言され、同じアセンブリで定義される必要があります。 タスク型が別の型内に入れ子になっている場合は、タスク ビルダー型もその同じ型で入れ子になっている必要があります。 注釈
非同期関数には、本体の await 式 (§12.9.8) を使用して評価を一時停止する機能があります。 後で、一時停止中の await 式の位置で、再開デリゲートを使用して評価を再開できます。 再開デリゲートは System.Action
型であり、呼び出されると、非同期関数呼び出しの評価は中断された await 式から再開されます。 非同期関数呼び出しの現在の呼び出し元は、関数呼び出しが中断されていない場合は元の呼び出し元、それ以外の場合は再開デリゲートの直近の呼び出し元です。
15.14.2 タスクタイプビルダーパターン
タスク ビルダー型は、最大で 1 つの型パラメーターを持ち、ジェネリック型で入れ子にすることはできません。 タスク ビルダー型には、宣言された SetResult
アクセシビリティを持つ次のメンバー (非ジェネリック タスク ビルダー型の場合、public
にはパラメーターがありません) が含まれている必要があります。
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
に関連付けるために呼び出されます。- ビルダーは、
stateMachine.MoveNext()
で、またはステート マシンを進めるためにStart()
が返された後に、Start()
を呼び出す必要があります。
- ビルダーは、
-
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 が完了したときにawaiter.UnsafeOnCompleted(action)
を呼び出すAction
でstateMachine.MoveNext()
を呼び出す必要があります。
-
- それ以外の場合、ステート マシンは
builder.AwaitOnCompleted(ref awaiter, ref stateMachine)
を呼び出します。-
AwaitOnCompleted()
は、awaiter が完了したときにawaiter.OnCompleted(action)
を呼び出すAction
でstateMachine.MoveNext()
を呼び出す必要があります。
-
-
SetStateMachine(IAsyncStateMachine)
は、特にステート マシンが値型として実装されている場合に、ステート マシン インスタンスに関連付けられているビルダーのインスタンスを識別するために、コンパイラによって生成されたIAsyncStateMachine
実装によって呼び出される場合があります。- ビルダーが
stateMachine.SetStateMachine(stateMachine)
を呼び出すと、stateMachine
は、builder.SetStateMachine(stateMachine)
に関連付けられている) でstateMachine
を呼び出します。
- ビルダーが
注:
SetResult(T result)
と«TaskType»<T> Task { get; }
の両方で、パラメーターと引数はそれぞれ、T
に同一性変換可能である必要があります。 これにより、タスク型ビルダーは、同じではない 2 つの型の同一性変換が可能なタプルなどの、型をサポートできます。 注釈
15.14.3 タスクを返す非同期関数の評価
タスクを返す非同期関数を呼び出すと、返されたタスク型のインスタンスが生成されます。 これは、非同期関数の戻りタスクと呼ばれます。 タスクは、最初は未完了の状態です。
非同期関数本体は、停止 (await 式に到達) するか終了するまで評価され、その時点で制御が戻りタスクと共に呼び出し元に返されます。
非同期関数の本体が終了すると、戻りタスクは未完了の状態から移動されます。
- return ステートメントまたは本文の末尾に到達した結果として関数本体が終了した場合、結果値は戻りタスクに記録され、成功の状態になります。
- 関数本体が、未処理の
OperationCanceledException
が原因で終了した場合、例外が戻りタスクに記録され、キャンセルの状態になります。 - 関数本体が他の未処理の例外 (§13.10.6) の結果として終了した場合、例外が戻りタスクに記録され、失敗の状態になります。
15.14.4 void を返す非同期関数の評価
非同期関数の戻り値型が void
の場合、評価は上記とは次の点で異なります。タスクが返されないため、関数は代わりに、現在のスレッドの同期コンテキストに完了と例外を伝達します。 同期コンテキストの正確な定義は実装に依存しますが、現在のスレッドが実行されている「場所」を表します。 同期コンテキストは、void
を返す非同期関数の評価が開始したとき、正常に完了したとき、または未処理の例外がスローされたときに通知されます。
これにより、コンテキストは、その下で実行中の void
を返す非同期関数の数を追跡し、それらから発生する例外を伝達する方法を決定できます。
15.15 同期反復子と非同期反復子
15.15.1 全般
反復子ブロック (§13.3) を使用して実装された関数メンバー (§12.6) またはローカル関数 (§13.6.4) は 反復子と呼ばれます。 反復子ブロックは、対応する関数メンバーの戻り値の型が列挙子インターフェイスの 1 つ (§15.15.2) または列挙可能なインターフェイス (§15.15.3) のいずれかである限り、関数メンバーの本体として使用できます。
反復子ブロック (§13.3) を使用して実装される非同期関数 (§15.14) は 、非同期反復子と呼ばれます。 非同期反復子ブロックは、対応する関数メンバーの戻り値の型が非同期列挙子インターフェイス (§15.15.2) または非同期列挙可能インターフェイス (§15.15.3) である限り、関数メンバーの本体として使用できます。
反復子ブロックは 、method_body、 operator_body 、または accessor_bodyとして発生する可能性があります。一方、イベント、インスタンス コンストラクター、静的コンストラクター、ファイナライザーは同期反復子または非同期反復子として実装されません。
関数メンバーまたはローカル関数が反復子ブロックを使用して実装されている場合、関数メンバーのパラメーター リストのコンパイル時エラーで、in
、out
、または ref
パラメーター、または ref struct
型のパラメーターを指定します。
15.15.2 列挙子インターフェイス
列挙子インターフェイスは、非ジェネリック インターフェイス System.Collections.IEnumerator
であり、ジェネリック インターフェイスのすべてのインスタンス化System.Collections.Generic.IEnumerator<T>
。
非同期列挙子インターフェイスは、ジェネリック インターフェイス System.Collections.Generic.IAsyncEnumerator<T>
のすべてのインスタンス化です。
簡潔にするために、このサブクラスとその兄弟では、これらのインターフェイスはそれぞれ IEnumerator
、 IEnumerator<T>
、および IAsyncEnumerator<T>
として参照されます。
15.15.3 列挙可能なインターフェイス
列挙可能なインターフェイスは、非ジェネリック インターフェイス System.Collections.IEnumerable
であり、ジェネリック インターフェイスのすべてのインスタンス化System.Collections.Generic.IEnumerable<T>
。
非同期列挙可能なインターフェイスは、ジェネリック インターフェイス System.Collections.Generic.IAsyncEnumerable<T>
のすべてのインスタンス化です。
簡潔にするために、このサブクラスとその兄弟では、これらのインターフェイスはそれぞれ IEnumerable
、 IEnumerable<T>
、および IAsyncEnumerable<T>
として参照されます。
15.15.4 収量の種類
反復子は、同じ型の値のシーケンスを生成します。 この型は、反復子の yield 型と呼ばれます。
-
IEnumerator
またはIEnumerable
を返す反復子の yield 型はobject
です。 -
IEnumerator<T>
、IAsyncEnumerator<T>
、IEnumerable<T>
、またはIAsyncEnumerable<T>
を返す反復子の yield 型がT
。
15.15.5 列挙子オブジェクト
15.15.5.1 全般
列挙子インターフェイス型を返す関数メンバーまたはローカル関数が反復子ブロックを使用して実装されている場合、関数を呼び出しても反復子ブロック内のコードは直ちに実行されません。 代わりに、列挙子オブジェクトが作成され返されます。 このオブジェクトは反復子ブロックで指定されたコードをカプセル化し、反復子ブロック内のコードの実行は、列挙子オブジェクトの MoveNext
または MoveNextAsync
メソッドが呼び出されたときに発生します。 列挙子オブジェクトには、次の特性があります。
-
System.IDisposable
、IEnumerator
、IEnumerator<T>
、またはSystem.IAsyncDisposable
とIAsyncEnumerator<T>
を実装します。ここで、T
は反復子の yield 型です。 - 関数メンバーに渡される引数値 (存在する場合) とインスタンス値のコピーを使用して初期化されます。
- これには、実行前、実行中、一時停止、および実行後の 4 つの潜在的な状態があり、初期状態は実行前です。
列挙子オブジェクトは通常、反復子ブロック内のコードをカプセル化し、列挙子インターフェイスを実装するコンパイラによって生成される列挙子クラスのインスタンスですが、他の実装メソッドも可能です。 列挙子クラスがコンパイラによって生成された場合、そのクラスは、関数メンバーを含むクラス内で直接または間接的に入れ子になり、プライベート アクセシビリティを持ち、コンパイラ用に予約された名前を持ちます (§6.4.3)。
列挙子オブジェクトは、上記で指定したインターフェイスよりも多くのインターフェイスを実装できます。
次のサブクラスでは、列挙子を進め、列挙子から現在の値を取得し、列挙子によって使用されるリソースを破棄するためにメンバーの必要な動作について説明します。 これらは、同期列挙子と非同期列挙子の次のメンバーでそれぞれ定義されています。
- 列挙子を進めるために:
MoveNext
とMoveNextAsync
。 - 現在の値を取得するには:
Current
。 - リソースを破棄するには:
Dispose
とDisposeAsync
。
列挙子オブジェクトは、IEnumerator.Reset
メソッドをサポートしていません。 このメソッドを呼び出すと、System.NotSupportedException
がスローされます。
同期反復子ブロックと非同期反復子ブロックは、非同期反復子メンバーがタスク型を返し、待機される可能性がある点が異なります。
15.15.5.2 列挙子を進める
列挙子オブジェクトの MoveNext
メソッドと MoveNextAsync
メソッドは、反復子ブロックのコードをカプセル化します。
MoveNext
またはMoveNextAsync
メソッドを呼び出すと、反復子ブロック内のコードが実行され、列挙子オブジェクトのCurrent
プロパティが適宜設定されます。
MoveNext
は、以下に説明する意味を持つ bool
値を返します。
MoveNextAsync
は ValueTask<bool>
(§15.14.3) を返します。
MoveNextAsync
から返されるタスクの結果値は、MoveNext
からの結果値と同じ意味を持ちます。 次の説明では、 MoveNext
について説明したアクションは、次の違いがある MoveNextAsync
に適用されます。ここで、 MoveNext
が true
または false
を返すと述べた場合、 MoveNextAsync
はタスクを 完了 状態に設定し、タスクの結果値を対応する true
または false
値に設定します。
MoveNext
またはMoveNextAsync
によって実行される正確なアクションは、呼び出されたときの列挙子オブジェクトの状態によって異なります。
- 列挙子オブジェクトの状態が実行前の場合、
MoveNext
を呼び出して、以下が実行されます。- 状態が実行中に変わります。
- 列挙子オブジェクトの初期化時に保存された引数値とインスタンス値に対して、反復子ブロックのパラメーター (
this
を含む) を初期化します。 - 開始時点から実行が中断されるまで反復子ブロックを実行します (以下で説明)。
- 列挙子オブジェクトの状態が実行中の場合、
MoveNext
を呼び出した結果は指定されていません。 - 列挙子オブジェクトの状態が一時停止の場合は、MoveNext を呼び出して以下が実行されます。
- 状態が実行中に変わります。
- 反復子ブロックの実行が最後に一時停止されたときに保存された値に、すべてのローカル変数とパラメーター (
this
を含む) の値を復元します。注: これらの変数によって参照されるすべてのオブジェクトの内容は、
MoveNext
の前回の呼び出し以降に変更されている可能性があります。 注釈 - 実行の停止の原因となった yield return ステートメントの直後に反復子ブロックの実行を再開し、実行が中断されるまで続行します (以下で説明)。
- 列挙子オブジェクトの状態が実行後の場合、
MoveNext
を呼び出すと false が返されます。
MoveNext
が反復子ブロックを実行すると、次の 4 通りの方法で実行が中断される可能性があります。yield return
ステートメント、yield break
ステートメント、反復子ブロックの末尾、および反復子ブロックからスローされて伝達される例外。
-
yield return
ステートメントが検出された場合 (§9.4.4.20):- ステートメントで指定された式が評価され、暗黙的に yield 型に変換され、列挙子オブジェクトの
Current
プロパティに代入されます。 - 反復子本体の実行が一時停止されます。 この
this
ステートメントの場所と同様に、すべてのローカル変数とパラメーター (yield return
を含む) の値が保存されます。yield return
ステートメントが 1 つ以上のtry
ブロック内にある場合、関連付けられた finally ブロックは現時点で実行されません。 - 列挙子オブジェクトの状態が一時停止に変更されます。
-
MoveNext
メソッドは呼び出し元にtrue
を返し、反復が次の値に正常に進んだことを示します。
- ステートメントで指定された式が評価され、暗黙的に yield 型に変換され、列挙子オブジェクトの
-
yield break
ステートメントが検出された場合 (§9.4.4.20):-
yield break
ステートメントが 1 つ以上のtry
ブロック内にある場合は、関連付けられているfinally
ブロックが実行されます。 - 列挙子オブジェクトの状態が実行後に変更されます。
-
MoveNext
メソッドは、反復処理が完了したことを示すfalse
を呼び出し元に返します。
-
- 反復子本体の末尾が検出された場合:
- 列挙子オブジェクトの状態が実行後に変更されます。
-
MoveNext
メソッドは、反復処理が完了したことを示すfalse
を呼び出し元に返します。
- 例外がスローされ、反復子ブロックから伝達された場合:
- 反復子本体の適切な
finally
ブロックは、例外伝達によって実行されます。 - 列挙子オブジェクトの状態が実行後に変更されます。
- 例外の伝達は、
MoveNext
メソッドの呼び出し元に引き続き実行されます。
- 反復子本体の適切な
15.15.5.3 現在の値を取得する
列挙子オブジェクトの Current
プロパティは、反復子ブロック内の yield return
ステートメントによって影響を受けます。
注:
Current
プロパティは、同期反復子オブジェクトと非同期反復子オブジェクトの両方の同期プロパティです。 注釈
列挙子オブジェクトが一時停止状態の場合、Current
の値は、MoveNext
の前回の呼び出しによって設定された値です。 列挙子オブジェクトが実行前、実行中、または実行後の状態にある場合、Current
にアクセスした結果は未指定です。
object
以外の yield 型を持つ反復子の場合、列挙子オブジェクトの Current
実装を介して IEnumerable
にアクセスした結果は、列挙子オブジェクトのCurrent
実装を介してIEnumerator<T>
にアクセスし、結果を object
にキャストすることに対応します。
15.15.5.4 リソースの破棄
Dispose
メソッドまたはDisposeAsync
メソッドは、列挙子オブジェクトを after 状態にすることで反復処理をクリーンアップするために使用されます。
- 列挙子オブジェクトの状態が実行前の場合、
Dispose
を呼び出すと、状態が実行後に変更されます。 - 列挙子オブジェクトの状態が実行中の場合、
Dispose
を呼び出した結果は指定されていません。 - 列挙子オブジェクトの状態が一時停止の場合、
Dispose
を呼び出して、以下が実行されます。- 状態が実行中に変わります。
- 最後に実行された
yield return
ステートメントがyield break
ステートメントであるかのように、finally ブロックを実行します。 これにより、例外がスローされ、反復子本体から伝達される場合、列挙子オブジェクトの状態は実行後に設定され、例外はDispose
メソッドの呼び出し元に伝達されます。 - 状態が実行後に変更されます。
- 列挙子オブジェクトの状態が実行後の場合、
Dispose
を呼び出しても何も影響はありません。
15.15.6 列挙可能なオブジェクト
15.15.6.1 全般
列挙可能なインターフェイス型を返す関数メンバーまたはローカル関数が反復子ブロックを使用して実装されている場合、関数メンバーを呼び出しても、反復子ブロック内のコードは直ちに実行されません。 代わりに、列挙可能オブジェクトが作成され返されます。
列挙可能なオブジェクトの GetEnumerator
または GetAsyncEnumerator
メソッドは、反復子ブロックで指定されたコードをカプセル化する列挙子オブジェクトを返します。反復子ブロック内のコードの実行は、列挙子オブジェクトの MoveNext
または MoveNextAsync
メソッドが呼び出されたときに発生します。 列挙可能オブジェクトには、次の特性があります。
-
IEnumerable
とIEnumerable<T>
またはIAsyncEnumerable<T>
を実装します。ここで、T
は反復子の yield 型です。 - 関数メンバーに渡される引数値 (存在する場合) とインスタンス値のコピーを使用して初期化されます。
列挙可能オブジェクトは通常、反復子ブロック内のコードをカプセル化し、列挙可能インターフェイスを実装するコンパイラによって生成される列挙可能クラスのインスタンスですが、他の実装メソッドも可能です。 列挙可能クラスがコンパイラによって生成された場合、そのクラスは、関数メンバーを含むクラス内で直接または間接的に入れ子になり、プライベート アクセシビリティを持ち、コンパイラ用に予約された名前を持ちます (§6.4.3)。
列挙可能オブジェクトは、上記で指定したインターフェイスよりも多くのインターフェイスを実装できます。
注: たとえば、列挙可能オブジェクトは、
IEnumerator
とIEnumerator<T>
を実装して、列挙可能オブジェクトと列挙子の両方として機能するようにすることもできます。 通常、このような実装では、GetEnumerator
の最初の呼び出しから (割り当てを保存するために) 独自のインスタンスが返されます。GetEnumerator
の後続の呼び出しがある場合は、通常は同じクラスの新しいクラス インスタンスが返されるため、異なる列挙子インスタンスへの呼び出しは互いに影響しません。 前回の列挙子がシーケンスの末尾を越えて既に列挙されている場合でも、同じインスタンスを返すことはできません。これは、今後、使い果たされた列挙子に対するすべての呼び出しで例外をスローする必要があるためです。 注釈
15.15.6.2 GetEnumerator または GetAsyncEnumerator メソッド
列挙可能オブジェクトは、GetEnumerator
インターフェイスと IEnumerable
インターフェイスの IEnumerable<T>
メソッドの実装を提供します。 2 つの GetEnumerator
メソッドは、使用可能な列挙子オブジェクトを取得して返す共通の実装を共有します。 列挙子オブジェクトは、列挙可能なオブジェクトが初期化されたときに保存された引数値とインスタンス値で初期化されますが、それ以外の場合、列挙子オブジェクトは §15.15.5 で説明されているように機能します。
非同期列挙可能オブジェクトは、GetAsyncEnumerator
インターフェイスのIAsyncEnumerable<T>
メソッドの実装を提供します。 このメソッドは、使用可能な非同期列挙子オブジェクトを返します。 列挙子オブジェクトは、列挙可能なオブジェクトが初期化されたときに保存された引数値とインスタンス値で初期化されますが、それ以外の場合、列挙子オブジェクトは §15.15.5 で説明されているように機能します。
ECMA C# draft specification