8.1 全般
C# 言語の型は、参照型と値型の 2 つの主要なカテゴリに分けられます。 値型と参照型はどちらも、1 つ以上の型パラメータを取るジェネリック型になる場合があります。 型パラメータでは、値型と参照型の両方を指定できます。
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) は、未使用のコード (§23) のみで利用できます。
値型は参照型とは異なり、値型の変数にはデータが直接格納されるのに対し、参照型の変数にはデータへの参照が格納され、後者はオブジェクトと呼ばれます。 参照型を使用すると 2 つの変数が同じオブジェクトを参照できるため、1 つの変数に対する演算によって、もう一方の変数によって参照されるオブジェクトに影響を与えることができます。 値型を使用すると、各々の変数がデータのコピーを各々で持ち、1 つに対する操作がもう一方に影響を与えることはできません。
注: 変数が参照または出力パラメータである場合、変数自体にはストレージがなく、別の変数のストレージを参照します。 この場合、ref または out 変数は実質的に別の変数のエイリアスであり、別個の変数ではありません。 注釈
C# の型システムは統一されており、あらゆる型の値をオブジェクトとして扱うことができます。 C# における型はすべて、直接的または間接的に object
クラス型から派生し、object
はすべての型の究極の基底クラスです。 参照型の値は、値を単純に object
型としてみなすことによってオブジェクトとして扱われます。 値型の値は、ボックス化とボックス化解除操作を実行することによって、オブジェクトとして扱われます (§8.3.13)。
便宜上、この仕様全体では、一部のライブラリ タイプ名は完全な名前修飾を使用せずに記述されています。 詳細については§C.5 を参照してください。
8.2 参照型
8.2.1 全般
参照型は、クラス型、インターフェイス型、配列型、デリゲート型、または dynamic
型です。 それぞれの null 非許容参照型には、型名に ?
を追加することで示される対応する null 許容参照型があります。
reference_type
: non_nullable_reference_type
| nullable_reference_type
;
non_nullable_reference_type
: class_type
| interface_type
| array_type
| delegate_type
| 'dynamic'
;
class_type
: type_name
| 'object'
| 'string'
;
interface_type
: type_name
;
array_type
: non_array_type rank_specifier+
;
non_array_type
: value_type
| class_type
| interface_type
| delegate_type
| 'dynamic'
| type_parameter
| pointer_type // unsafe code support
;
rank_specifier
: '[' ','* ']'
;
delegate_type
: type_name
;
nullable_reference_type
: non_nullable_reference_type nullable_type_annotation
;
nullable_type_annotation
: '?'
;
pointer_type は、未使用のコード (§23.3) のみで利用できます。 nullable_reference_typeは、§8.9 で後述します。
参照型の値は、型のインスタンスへの参照であり、後者はオブジェクトと呼ばれます。 特別な値 null
はすべての参照型と互換性があり、インスタンスが存在しないことを示します。
8.2.2 クラス型
クラス型は、データ メンバー (定数とフィールド)、関数メンバー (メソッド、プロパティ、イベント、インデクサ、演算子、インスタンス コンストラクタ、ファイナライザ、および静的コンストラクタ)、およびネストされた型を含むデータ構造を定義します。 クラス型は継承をサポートします。継承とは、派生クラスが基本クラスを拡張および特殊化できるメカニズムです。 クラス型のインスタンスは、object_creation_expressions (§12.8.17.2) を使用して作成されます。
クラス型については §15 で説明します。
特定の定義済みクラス型は、次の表に示すように、C# 言語では特別な意味を持ちます。
クラス型 | 説明 |
---|---|
System.Object |
その他すべての型の最終的な基底クラス。 「§8.2.3」を参照してください。 |
System.String |
C# 言語の文字列型。 「§8.2.5」を参照してください。 |
System.ValueType |
すべての値型の基底クラス。 「§8.3.2」を参照してください。 |
System.Enum |
すべての enum 型の基底クラス。 「§19.5」を参照してください。 |
System.Array |
すべての配列型の基底クラス。 「§17.2.2」を参照してください。 |
System.Delegate |
すべての delegate 型の基底クラス。 「§20.1」を参照してください。 |
System.Exception |
すべての例外型の基底クラス。 「§21.3」を参照してください。 |
8.2.3 オブジェクト型
object
クラス型は、その他すべての型の最終的な基底クラスです。 C# のすべての型は、直接的または間接的に object
クラス型から派生します。
キーワード object
は、定義済みクラス System.Object
の単なるエイリアスです。
8.2.4 動的型
dynamic
などの object
型は、オブジェクトを参照できます。 型 dynamic
の式に演算が適用されると、その解決はプログラムが実行されるまで延期されます。 したがって、参照先のオブジェクトに操作を正当に適用できない場合、コンパイル中にエラーは発生しません。 代わりに、実行時に操作の解決が失敗すると例外がスローされます。
dynamic
型については §8.7 で、動的バインディングについては §12.3.1 で詳述しています。
8.2.5 文字列型
string
型は、object
から直接継承される sealed クラス型です。
string
クラスのインスタンスは Unicode 文字列を表します。
string
型の値は、文字列リテラルとして記述できます (§6.4.5.6)。
キーワード string
は、定義済みクラス System.String
の単なるエイリアスです。
8.2.6 インターフェイス型
インターフェイスによりコントラクトが定義されます。 インターフェイスを実装するクラスまたは構造体は、その契約に従う必要があります。 インターフェイスは複数の基本インターフェイスから継承することができ、クラスまたは構造体は複数のインターフェイスを実装することができます。
インターフェイス型については、§18 で説明します。
8.2.7 配列型
配列は、計算されたインデックス経由でアクセスされる 0 個以上の変数を含むデータ構造です。 配列に含まれる変数は配列の要素とも呼ばれ、すべて同じ型であり、この型は配列の要素型と呼ばれます。
配列型については、§17 で説明します。
8.2.8 デリゲート型
デリゲートは、1 つ以上のメソッドを参照するデータ構造です。 インスタンス メソッドの場合、対応するオブジェクト インスタンスも参照します。
注: C または C++ のデリゲートに最も近いものは関数ポインターですが、関数ポインターは静的関数のみを参照できるのに対し、デリゲートは静的メソッドとインスタンス メソッドの両方を参照できます。 後者の場合、デリゲートはメソッドのエントリ ポイントへの参照だけでなく、メソッドを呼び出すオブジェクト インスタンスへの参照も格納します。 注釈
デリゲート型については、§20 で説明します。
8.3 値型
8.3.1 全般
値型は、構造体型または列挙型のいずれかです。 C# には、単純型と呼ばれる定義済みの構造型のセットが用意されています。 単純型はキーワードによって識別されます。
value_type
: non_nullable_value_type
| nullable_value_type
;
non_nullable_value_type
: struct_type
| enum_type
;
struct_type
: type_name
| simple_type
| tuple_type
;
simple_type
: numeric_type
| 'bool'
;
numeric_type
: integral_type
| floating_point_type
| 'decimal'
;
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
;
floating_point_type
: 'float'
| 'double'
;
tuple_type
: '(' tuple_type_element (',' tuple_type_element)+ ')'
;
tuple_type_element
: type identifier?
;
enum_type
: type_name
;
nullable_value_type
: non_nullable_value_type nullable_type_annotation
;
参照型の変数とは異なり、値型の変数は、値型が null 許容値型である場合にのみ値 null
を含めることができます (§8.3.12)。 すべての null 非許容値型には、同じ値セットと値 null
を表す対応する null 許容値型が存在します。
値型の変数に割り当てると、割り当てられる値のコピーが作成されます。 これは、参照型の変数への代入とは異なります。参照型の変数への代入では、参照はコピーされますが、参照によって識別されるオブジェクトはコピーされません。
8.3.2 System.ValueType 型
すべての値型は、クラス class
から継承される System.ValueType
object
から暗黙的に継承され。 いかなる型も値型から派生することはできないため、値型は暗黙的にシールドされます (§15.2.2.3)。
System.ValueType
自体は、value_type ではないのでご注意ください。 むしろ、これはすべての value_type が自動的に派生される class_type です。
8.3.3 デフォルト コンストラクタ
すべての値型は、デフォルト コンストラクタと呼ばれる、パラメーターなしのパブリック インスタンス コンストラクタを暗黙的に宣言します。 デフォルト コンストラクタは、値型のデフォルト値と呼ばれるゼロ初期化インスタンスを返します。
- すべての simple_types の場合、デフォルト値はすべてゼロのビット パターンによって生成された値です。
-
sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
の場合、デフォルト値は、0
です。 -
char
の場合、デフォルト値は、'\x0000'
です。 -
float
の場合、デフォルト値は、0.0f
です。 -
double
の場合、デフォルト値は、0.0d
です。 -
decimal
の場合、デフォルト値は、0m
です (つまり、スケール 0 の値 0)。 -
bool
の場合、デフォルト値は、false
です。 -
enum_type
E
の場合、デフォルト値は、0
で、型E
に変換されます。
-
-
struct_type の場合、デフォルト値は、すべての値型フィールドをデフォルト値に設定し、すべての参照型フィールドを
null
に設定することによって生成される値です。 -
nullable_value_type の場合、デフォルト値は
HasValue
プロパティが false であるインスタンスです。 そのデフォルト値は、null 許容値型の null 値とも呼ばれます。 このような値のValue
プロパティを読み取ろうとするとSystem.InvalidOperationException
、型の例外がスローされます (§8.3.12)。
他のインスタンス コンストラクタと同様に、値型のデフォルト コンストラクタは new
演算子を使用して呼び出されます。
注: 効率上の理由から、この要件は実装で実際にコンストラクタ呼び出しを生成することを意図していません。 値型の場合、デフォルト値式 (§12.8.21) は、デフォルト コンストラクタを使用する場合と同じ結果を生成します。 注釈
例: 以下のコードでは、変数
i
、j
、k
は、ゼロに初期化されます。class A { void F() { int i = 0; int j = new int(); int k = default(int); } }
終了サンプル
すべての値型には暗黙的にパブリックのパラメータなしのインスタンス コンストラクタがあるため、構造体型にパラメータなしのコンストラクタの明示的な宣言を含めることはできません。 ただし、構造体型では、パラメータ化されたインスタンス コンストラクタを宣言できます (§16.4.9)。
8.3.4 構造体型
構造体型は、定数、フィールド、メソッド、プロパティ、イベント、インデクサ、演算子、インスタンス コンストラクタ、静的コンストラクタ、およびネストされた型を宣言できる値型です。 構造体の宣言については §16 で説明されています。
8.3.5 単純型
C# には、単純型と呼ばれる定義済みの struct
型のセットが用意されています。 単純型はキーワードを使用して識別されますが、次の表に示すように、これらのキーワードは struct
名前空間の定義済み System
型の単なるエイリアスです。
キーワード | エイリアス化された型 |
---|---|
sbyte |
System.SByte |
byte |
System.Byte |
short |
System.Int16 |
ushort |
System.UInt16 |
int |
System.Int32 |
uint |
System.UInt32 |
long |
System.Int64 |
ulong |
System.UInt64 |
char |
System.Char |
float |
System.Single |
double |
System.Double |
bool |
System.Boolean |
decimal |
System.Decimal |
単純型は構造体型のエイリアスとなるため、すべての単純型にはメンバーが存在します。
例:
int
にはSystem.Int32
で宣言されたメンバーとSystem.Object
から継承されたメンバーがあり、次のステートメントが許可されます。int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance method
終了サンプル
注: 単純型は、ある追加の操作を許可している点で、他の構造体型とは異なります。
- ほとんどの単純型では、リテラルを記述することによって値を作成できます (§6.4.5)。ただし、C# では一般に構造体のリテラルについては規定されていません。 例:
123
は、型int
のリテラルで、'a'
は、char
のリテラルです。 終了サンプル- 式のオペランドがすべて単純型定数である場合、コンパイラはコンパイル時に式を評価できます。 このような式は constant_expression (§12.23) と呼ばれます。 他の構造体型で定義された演算子を含む式は定数式とはみなされません。
const
宣言を通じて、単純型の定数を宣言することができます (§15.4)。 他の構造体型の定数を持つことはできませんが、静的読み取り専用フィールドによって同様の効果が提供されます。- 単純型に関係する変換は、他の構造体型によって定義された変換演算子の評価に参加できますが、ユーザー定義の変換演算子は、別のユーザー定義の変換演算子の評価に参加することはできません (§10.5.3)。
注釈。
8.3.6 整数型
C# は、sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
の 9 つの整数型をサポートしています 整数型には次のサイズと値の範囲があります。
-
sbyte
型は、-128
から127
までの値を持つ符号付き 8 ビット整数を表します。 -
byte
型は、0
から255
までの値を持つ符号なし 8 ビット整数を表します。 -
short
型は、-32768
から32767
までの値を持つ符号付き 16 ビット整数を表します。 -
ushort
型は、0
から65535
までの値を持つ符号なし 16 ビット整数を表します。 -
int
型は、-2147483648
から2147483647
までの値を持つ符号付き 32 ビット整数を表します。 -
uint
型は、0
から4294967295
までの値を持つ符号なし 32 ビット整数を表します。 -
long
型は、-9223372036854775808
から9223372036854775807
までの値を持つ符号付き 64 ビット整数を表します。 -
ulong
型は、0
から18446744073709551615
までの値を持つ符号なし 64 ビット整数を表します。 -
char
型は、0
から65535
までの値を持つ符号なし 16 ビット整数を表します。char
型の可能な値のセットは、Unicode 文字セットに対応します。注:
char
はushort
と同じ表現ですが、一方の型で許可されているすべての操作がもう一方の型でも許可されるわけではありません。 注釈
すべての符号付き整数型は 2 の補数形式を使用して表されます。
integral_type の単項演算子と二項演算子は、§12.4.7 で詳述されているように、常に符号付き 32 ビット精度、符号なし 32 ビット精度、符号付き 64 ビット精度、または符号なし 64 ビット精度で動作します。
char
型は整数型として分類されますが、他の整数型とは 2 つの点で異なります。
- 他の型から
char
型への定義済みの暗黙的な変換はありません。 特に、byte
型とushort
型はchar
型を使用して完全に表現可能な値の範囲を持っていますが、sbyte、byte、またはushort
からchar
への暗黙的な変換は存在しません。 -
char
型の定数は、character_literal として記述するか、char 型へのキャストと組み合わせて integer_literal として記述する必要があります。
例:
(char)10
は、'\x000A'
と同じです。 終了サンプル
checked
および unchecked
演算子は、整数型の算術演算および変換に対するオーバーフロー チェックを制御するために使用します (§12.8.20)。
checked
コンテキストでは、オーバーフローによりコンパイル時エラーが生成されるか、System.OverflowException
がスローされます。
unchecked
コンテキストでは、オーバーフローは無視され、宛先タイプに適合しない上位ビットは破棄されます。
8.3.7 浮動小数点型
C# は、float
と double
の 2 つの浮動小数点型をサポートします。
float
および double
型は、32 ビット単精度および 64 ビット倍精度の IEC 60559 形式を使用して表され、次の値のセットが提供されます。
- 正のゼロと負のゼロ。 ほとんどの場合、正のゼロと負のゼロは単純なゼロの値と同じように動作しますが、特定の操作ではこの2つを区別します (§12.10.3)。
- 正の無限大と負の無限大。 無限大は、ゼロ以外の数字をゼロで割るような演算で生成されます。
例:
1.0 / 0.0
は正の無限大、–1.0 / 0.0
は負の無限大です。 終了サンプル - Not-a-Number (数字ではない) 値は、NaN と略されることがあります。 NaN は、ゼロをゼロで割るなど、無効な浮動小数点演算で生成されます。
-
s × m × 2ᵉ の 形式の非ゼロ値の有限集合では、s が 1 または −1 で、m および e は特定の浮動小数点型によって決まります。
float
の場合、0 <m< 2²⁴ and −149 ≤ e ≤ 104 であり、double
の場合、0 <m< 2⁵³ and −1075 ≤ e ≤ 970 です。 非正規化浮動小数点数は有効な非ゼロ値とみなされます。 C# では、準拠実装が非正規化浮動小数点数をサポートすることを要求も禁止もしていません。
float
型は、7 桁の精度で約 1.5 × 10⁻⁴⁵ から 3.4 × 10³⁸ までの範囲の値を表すことができます。
double
型は、15 から 17 桁の精度で約 5.0 × 10⁻³²⁴ から 1.7 × 10³⁰⁸ までの範囲の値を表すことができます。
二項演算子のいずれかのオペランドが浮動小数点型の場合、§12.4.7 に詳述されているように標準の数値昇格が適用され、float
または double
精度で演算が実行されます。
代入演算子を含む浮動小数点演算子は、例外を生成することはありません。 代わりに、例外的な状況では、浮動小数点演算によって、以下に示すようにゼロ、無限大、または NaN が生成されます。
- 浮動小数点演算の結果は、変換先の形式で表現可能な最も近い値で四捨五入されます。
- 浮動小数点演算の結果の大きさが変換先の形式に対して小さすぎる場合、演算の結果は正のゼロまたは負のゼロになります。
- 浮動小数点演算の結果の大きさが変換先の形式に対して大きすぎる場合、演算の結果は正の無限大または負の無限大になります。
- 浮動小数点演算が無効な場合、演算の結果は NaN になります。
- 浮動小数点演算のオペランドの一方または両方が NaN の場合、演算の結果は NaN になります。
浮動小数点演算は、演算の結果の型よりも高い精度で実行される場合があります。 浮動小数点型の値をその型の正確な精度に強制するには、明示的なキャスト (§12.9.7) を使用できます。
例: 一部のハードウェア アーキテクチャでは、
double
型よりも範囲と精度が広い「extended」または「long double」浮動小数点型をサポートしており、すべての浮動小数点演算をこのより高い精度の型を使用して暗黙的に実行します。 このようなハードウェア アーキテクチャでは、パフォーマンスを大幅に犠牲にすることでのみ、精度の低い浮動小数点演算を実行できます。C# では、パフォーマンスと精度の両方を犠牲にする実装を要求するのではなく、すべての浮動小数点演算に高精度の型を使用できます。 より正確な結果をもたらす以外には、測定可能な効果はほとんどありません。 ただし、乗算によってx * y / z
> の範囲外の結果が生成され、その後の除算によって一時的な結果がdouble
の範囲に戻されるdouble
形式の式では、式がより広い範囲の形式で評価されるため、無限大ではなく有限の結果が生成されることがあります。 終了サンプル
8.3.8 Decimal 型
decimal
型は 128 ビットのデータ型で、財務や通貨の計算に適しています。
decimal
型は、少なくとも -7.9 × 10⁻²⁸ から 7.9 × 10²⁸ までの範囲の値を、少なくとも 28 桁の精度で表現できます。
型 decimal
の値の有限集合は (–1)ᵛ × c × 10⁻ᵉ の形式で、符号 v は、0 または 1、係数 c は 0 ≤ c<Cmax で指定され、スケール e は、Emin ≤ e ≤ Emax となり、ここでは、Cmax は少なくとも 1 × 10²⁸、Emin ≤ 0 および Emax ≥ 28 となります。
decimal
型は必ずしも符号付きゼロ、無限大、または NaN をサポートするわけではありません。
decimal
は 10 の累乗でスケールされた整数として表されます。 絶対値が decimal
未満の 1.0m
の場合、値は少なくとも小数点第 28 位まで正確です。 絶対値が decimal
以上である 1.0m
の場合、値は少なくとも小数点第 28 位まで正確です。
float
および double
データ型とは異なり、0.1
などの小数は、小数表現で正確に表現できます。
float
および double
表現では、このような数値は多くの場合、終了しないバイナリ展開を持ち、それらの表現では切り捨てエラーが発生しやすくなります。
二項演算子のいずれかのオペランドが decimal
型の場合、§12.4.7 に詳述されているように標準の数値昇格が適用され、演算は、double
精度で実行されます。
decimal
型の値に対する演算の結果は、正確な結果を計算し (各演算子の定義に従ってスケールを保持)、表現で四捨五入します。 結果は最も近い表現可能な値で四捨五入され、結果が 2 つの表現可能な値に等しく近い場合は、最下位桁の位置に偶数を持つ値で四捨五入されます (これは「銀行型丸め」と呼ばれます)。 つまり、結果は少なくとも小数点第 28 位まで正確です。 丸めによって、ゼロ以外の値からゼロ値が生成される場合があることに注意してください。
decimal
算術演算によって生成された結果の大きさが decimal
形式には大きすぎる場合、System.OverflowException
がスローされます。
decimal
型は浮動小数点型よりも精度は高くなりますが、範囲は狭くなる可能性があります。 したがって、浮動小数点型から decimal
への変換ではオーバーフロー例外が発生する可能性があり、decimal
から浮動小数点型への変換では精度の低下やオーバーフロー例外が発生する可能性があります。 これらの理由により、浮動小数点型と decimal
の間には暗黙的な変換は存在せず、明示的なキャストがない場合、浮動小数点オペランドと decimal
オペランドが同じ式内に直接混在するとコンパイル時エラーが発生します。
8.3.9 ブール型
bool
型はブール論理量を表します。 型 bool
に指定できる値は、true
および false
です。
false
の表現は §8.3.3で説明されています。
true
の表現方法は特に指定しませんが、false
の表現方法とは異なります。
bool
と他の値型の間には標準的な変換は存在しません。 特に、bool
型は整数型とは区別され分離されているため、整数値の代わりに bool
値を使用することはできず、その逆も同様です。
注: C 言語および C++ 言語では、ゼロの整数値または浮動小数点値、または NULL ポインタをブール値
false
に変換でき、ゼロ以外の整数値または浮動小数点値、または NULL 以外のポインタをブール値true
に変換できます。 C# では、このような変換は、整数値または浮動小数点値を 0 と明示的に比較するか、オブジェクト参照をnull
と明示的に比較することによって実行されます。 注釈
8.3.10 列挙型
列挙型は、名前付き定数を持つ固有の型です。 すべての列挙型には基礎となる型があり、それは、byte
、sbyte
、short
、ushort
、int
、uint
、long
または ulong
です。 列挙型の値のセットは、その基になる型の値のセットと同じです。 列挙型の値は、名前付き定数の値に制限されません。 列挙型は列挙宣言 (§19.2) をで定義されます。
8.3.11 タプル型
タプル型は、オプションの名前と個別の型を持つ、順序付けられた固定長の値のシーケンスを表します。 タプル型内の要素の数は、アリティと呼ばれます。 タプル型は、n≥2 である (T1 I1, ..., Tn In)
で記述され、ここでは、識別子 I1...In
はオプションのタプル要素名です。
この構文は、型 T1...Tn
から System.ValueTuple<...>
で構築された型の省略形であり、2 から 7 までの任意のアリティのタプル型を直接表現できる汎用構造体型のセットになります。
対応する数の型パラメータを持つタプル型のアリティに直接一致する System.ValueTuple<...>
宣言が存在する必要はありません。 代わりに、7 を超えるアリティを持つタプルは、タプル要素に加えて、別の System.ValueTuple<T1, ..., T7, TRest>
型を使用して残りの要素のネストされた値を含む Rest
フィールドを持つジェネリック構造体型 System.ValueTuple<...>
で表されます。 このようなネスティングは、たとえば、Rest
フィールドの存在など、さまざまな方法で見られます。 1 つの追加フィールドのみが必要な場合、ジェネリック構造体型 System.ValueTuple<T1>
が使用されます。この型自体はタプル型とは見なされません。 7 つ以上の追加フィールドが必要な場合は、System.ValueTuple<T1, ..., T7, TRest>
が再帰的に使用されます。
タプル型内の要素名は、異なる名前である必要があります。
ItemX
形式のタプル要素名 (X
はタプル要素の位置を表す、0
で始まらない任意の 10 進数字のシーケンス) は、X
で示される位置でのみ許可されます。
オプションの要素名は ValueTuple<...>
型では表現されず、タプル値の実行時間表現には格納されません。 変換不要な要素型並びがあるタプル間には同一性変換 (§10.2.2) が存在します。
new
演算子 §12.8.17.2 は、タプル型構文 new (T1, ..., Tn)
には適用できません。 タプル値はタプル式 (§12.8.6) から作成するか、new
から構築された型に ValueTuple<...>
演算子を直接適用することで作成できます。
タプル要素は、Item1
、Item2
などの名前を持つパブリック フィールドであり、タプル値のメンバー アクセス経由でアクセスできます (§12.8.7)。 さらに、タプル型に特定の要素の名前がある場合は、その名前を使用して問題の要素にアクセスできます。
注: 大きなタプルがネストされた
System.ValueTuple<...>
値で表されている場合でも、各タプル要素には、その位置に対応するItem...
名を使用して直接アクセスできます。 注釈
例: 次の例について考えます。
(int, string) pair1 = (1, "One"); (int, string word) pair2 = (2, "Two"); (int number, string word) pair3 = (3, "Three"); (int Item1, string Item2) pair4 = (4, "Four"); // Error: "Item" names do not match their position (int Item2, string Item123) pair5 = (5, "Five"); (int, string) pair6 = new ValueTuple<int, string>(6, "Six"); ValueTuple<int, string> pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");
pair1
、pair2
およびpair3
のタプル型はすべて有効で、タプル型要素の一部またはすべてに名前が付いています。
pair4
のタプル型は、名前Item1
とItem2
の位置が一致するため有効ですが、名前pair5
およびItem2
の位置は一致しないため、Item123
タプル型は許可されません。
pair6
とpair7
の宣言は、タプル型が形式ValueTuple<...>
の構築型と交換可能であり、後者の構文ではnew
演算子が許可されていることを示しています。最後の行は、タプル要素がその位置に対応する
Item
名でアクセスできるだけでなく、型内に存在する場合は対応するタプル要素名でもアクセスできることを示しています。 終了サンプル
8.3.12 null 許容値型
null 許容値照型は、基底となる型のすべての値と追加の null 値を表します。 null 許容値型は、T?
と記述され、ここでは T
は、基底型です。 この構文は System.Nullable<T>
の省略形であり、2 つの形式は互換的に使用できます。
逆に、null 非許容値型は、System.Nullable<T>
以外の任意の値型とその省略形 T?
(任意の T
の場合) で、null 非許容値型として制約される任意の型パラメータ (つまり、値型制約 (§15.2.5) を持つ任意の型パラメータ) です。
System.Nullable<T>
型は T
の値型の制約を指定します。つまり、null 許容値型の基になる型は、null 非許容値型にすることができます。 null 許容値型の基になる型は、null 許容値型または参照型にはできません。 たとえば、int??
は、無効な型です。 null 許容参照型については、§8.9 を参照してください。
null 許容値型 T?
のインスタンスには、2 つのパブリック読み取り専用プロパティがあります。
- 型
HasValue
のbool
プロパティ - 型
Value
のT
プロパティ
HasValue
が true
のインスタンスは、null ではないと認識されています。 null ではなインスタンスには、既知の値が含まれ、Value
は、その値を返します。
HasValue
が false
のインスタンスは、null と認識されています。 null インスタンスには、未定義の値があります。 null インスタンスの Value
を読み込もうとすると System.InvalidOperationException
がスローされます。 null 許容インスタンスの Value プロパティにアクセスするプロセスは、アンラップと呼ばれています。
デフォルトのコンストラクタに加え、各 null 許容値型 T?
には、型 T
の単一パラメータがあるパブリック コンストラクタがあります。 型 x
の 値 T
が指定された場合、形式のコンストラクタ呼び出しにより
new T?(x)
T?
プロパティが Value
である x
の null 以外のインスタンスが作成されます。 特定の値に対して null 許容値型の null 以外のインスタンスを作成するプロセスは、ラッピングと呼ばれています。
暗黙的な変換は null
リテラルから T?
(§10.2.7) および T
から T?
(§10.2.6) で利用できます。
null 許容値型 T?
はインターフェイスを実装しません (§18)。 特に、これは、基礎となる型 T
が実装するインターフェースを実装していないことを意味します。
8.3.13 ボックス化とボックス化解除
ボックス化とアンボックス化の概念は、型 間で変換される value_type の任意の値を許可することで value_type および object
間を橋渡しします。 ボックス化とアンボックス化により、型システムの統一されたビューが可能になり、任意の型の値を最終的に object
として扱うことができます。
ボックス化については §10.2.9 を、アンボックス化については §10.3.7 を参照してください。
8.4 構築型
8.4.1 全般
ジェネリック型宣言は、それ自体が未束縛ジェネリック型を表し、これは型引数を適用することで、さまざまな型を形成するための「ブループリント」として使用されます。 型引数は、ジェネリック型の名前の直後に山括弧 (<
および >
) で囲んで記述されます。 少なくとも 1 つの型引数を含む型は、構築型と呼ばれます。 構築型は、型名が出現できる言語のほとんどの場所で使用できます。 未束縛ジェネリック型は typeof_expression (§12.8.18) 内でのみ使用できます。
構築型は、式の中で単純な名前として (§12.8.4) 使用でき、またメンバーにアクセスするときにも使用できます (§12.8.7)。
namespace_or_type_name が評価される際は、正しい数の型パラメータを持つジェネリック型のみが考慮されます。 したがって、型パラメータの数が異なる限り、同じ識別子を使用して異なる型を識別することができます。 これは、同じプログラム内でジェネリック クラスと非ジェネリック クラスを混在させる場合に便利です。
例:
namespace Widgets { class Queue {...} class Queue<TElement> {...} } namespace MyApplication { using Widgets; class X { Queue q1; // Non-generic Widgets.Queue Queue<int> q2; // Generic Widgets.Queue } }
終了サンプル
namespace_or_type_name 生成物における名前検索の詳細な規則につては、§7.8 を参照してください。 これらの生成物の曖昧さの解決については、§6.2.5 を参照してください。
type_name は、型パラメータを直接指定していなくても、構築型を識別する場合があります。 これは、型がジェネリック class
宣言内にネストされ、それを含む宣言のインスタンスの型が名前の検索に暗黙的に使用される場合に発生する可能性があります (§15.3.9.7)。
例:
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }
終了サンプル
非列挙構築型は unmanaged_type として使用できません (§8.8)。
8.4.2 型引数
型引数リストの各引数は、純粋に型です。
type_argument_list
: '<' type_argument (',' type_argument)* '>'
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
各型引数は対応する型パラメータに対する制約を満たす必要があります (§15.2.5)。 参照型引数の null 可能性が型パラメータの null 可能性と一致しない場合、制約は満たされますが、警告が表示される場合があります。
8.4.3 オープン型およびクローズド型
すべてのタイプは、オープン型またはクローズド型に分類できます。 オープン型は、型パラメータを含む型です。? 具体的には次のとおりです。
- 型パラメータは、オープン型を定義します。
- 配列型は、その要素型がオープン型の場合のみ、オープン型になります。
- 構築型は、その型引数の 1 つ以上がオープン型である場合にのみ、オープン型になります。 構築ネスト型は、その型引数の 1 つ以上、またはそれに含まれる型の 1 つ以上の型引数がオープン型である場合にのみ、オープン型になります。
クローズド型はオープン型ではない型です。
実行時に、ジェネリック型宣言内のすべてのコードは、ジェネリック宣言に型引数を適用することによって作成されたクローズ構築型のコンテキストで実行されます。 ジェネリック型内の各型パラメータは、特定のランタイム型にバインドされます。 すべてのステートメントと式の実行時処理は常にクローズド型で行われ、オープン型はコンパイル時処理中にのみ行われます。
同じ未束縛ジェネリック型から構築され、対応する型引数のそれぞれの間に同一性変換が存在する場合、2 つのクローズド構築型は、同一性変換可能 (§10.2.2) です。 対応する型引数自体は、クローズド構築型、または同一性変換可能なタプルである場合があります。 同一性変換可能なクローズド構築型は、単一の静的変数セットを共有します。 それ以外の場合、それぞれのクローズド構築型には独自の静的変数のセットが存在します。 オープン型は実行時には存在しないため、オープン型に関連付けられた静的変数は存在しません。
8.4.4 束縛型および未束縛型
未束縛型という用語は、非ジェネリック型または未束縛ジェネリック型を指します。 束縛型という用語は、非ジェネリック型または構築型を指します。
未束縛型は、型宣言によって宣言されたエンティティを指します。 未束縛ジェネリック型自体は、型ではなく、変数の型、引数または戻り値または基底型として使用できません。 未束縛ジェネリック型を参照できる唯一の構造は、typeof
式です (§12.8.18)。
8.4.5 制約を満たす
構築型またはジェネリック メソッドが参照されるたびに、指定の型引数はジェネリック型またはメソッドで宣言された型パラメータ制約と照合します (§15.2.5)。 各 where
句の場合、名前付き型パラメータに対応する型引数 A
が次のように各制約に対してチェックされます。
- 制約が
class
型、インターフェイス型、または型パラメータである場合、制約内に現れる型パラメータの代わりに指定された型引数を使用して、その制約をC
で表します。 制約を満たすには、型A
が次のいずれかによって型C
に変換可能でなければなりません。 - 制約が参照型制約 (
class
) の場合、型A
は次のいずれかを満たす必要があります。-
A
は、インターフェイス型、クラス型、デリゲート型、配列型、または動的型です。
注:
System.ValueType
およびSystem.Enum
はこの制約を満たす参照型です。 注釈-
A
は、参照型として知られている型パラメータです (§8.2)。
-
- 制約が値型制約 (
struct
) の場合、型A
は次のいずれかを満たす必要があります。-
A
は、struct
型またはenum
ですが、null 許容値型ではありません。
注:
System.ValueType
およびSystem.Enum
はこの制約を満たさない参照型です。 注釈-
A
は、値型制約がある型パラメータです (§15.2.5)。
-
- 制約が、コンストラクタ制約
new()
の場合、型A
は、abstract
にならず、一般的なパラメータなしのコンストラクタになります。 これは、次のいずれかが True の場合に満たされます。
指定された型引数によって型パラメータの制約の 1 つ以上が満たされない場合、コンパイル時エラーが発生します。
型パラメータは継承されないため、制約も継承されません。
例: 以下では、
D
は型パラメータT
に制約を指定して、T
がclass
B<T>
基底クラスによって課された制約を満たすようにする必要があります。 対照的に、class
は任意のE
に対してList<T>
を実装するため、IEnumerable
T
は制約を指定する必要はありません。class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
終了サンプル
8.5 型パラメーター
型パラメータは、実行時にパラメータがバインドされる値型または参照型を指定する識別子です。
type_parameter
: identifier
;
型パラメータはさまざまな型引数を使用してインスタンス化できるため、型パラメータには他の型と若干異なる操作と制限があります。
注: これには次が含まれます。
- 型パラメータは、基底クラス (§15.2.4.2) またはインターフェイス (§18.2.4) を宣言するために直接使用することはできません。
- 型パラメータのメンバー検索のルールは、型パラメータに適用された制約 (存在する場合) によって異なります。 詳細については、§12.5 を参照してください。
- 型パラメータに使用できる変換は、型パラメータに適用された制約 (存在する場合) によって異なります。 詳細については、§10.2.12 および §10.3.8 を参照してください。
- 型パラメータが参照型であることが分かっている場合を除き、リテラル
null
は型パラメータによって指定された型に変換できません (§10.2.12)。 ただし、代わりにデフォルト式 (§12.8.21) を使用することもできます。 さらに、型パラメータによって指定された型の値は、型パラメータに値型制約がない限り、 および==
(!=
) を使用して null と比較できます。new
式 (§12.8.17.2) は、型パラメータが constructor_constraint または値型制約 (§15.2.5) によって制約されている場合にのみ、型パラメータで使用できます。- 型パラメーターは属性内のどこにも使用できません。
- 型パラメータは、静的メンバーまたはネストされた型を識別するためにメンバー アクセス (§12.8.7) または型名 (§7.8) で使用することはできません。
- 型パラメータは unmanaged_type として使用できません (§8.8)。
注釈
型として、型パラメータは純粋にコンパイル時の構成要素です。 実行時に、各型パラメータは、ジェネリック型宣言に型引数を提供することによって指定された実行時型にバインドされます。 したがって、型パラメータで宣言された変数の型は、実行時に閉じた構築型になります §8.4.3。 型パラメータを含むすべてのステートメントと式の実行時実行では、そのパラメータの型引数として指定された型が使用されます。
8.6 式ツリー型
式ツリー を使用すると、ラムダ式を実行可能コードではなくデータ構造として表現できます。 式ツリーは、形式 のの値で、ここでは、System.Linq.Expressions.Expression<TDelegate>
は任意のデリゲート型です。 この仕様の残りの部分では、これらの型は省略形 Expression<TDelegate>
を使用して参照されます。
ラムダ式からデリゲート型 D
への変換が存在する場合、式ツリー型 Expression<TDelegate>
への変換も存在します。 ラムダ式をデリゲート型に変換すると、ラムダ式の実行可能コードを参照するデリゲートが生成されますが、式ツリー型に変換すると、ラムダ式の式ツリー表現が作成されます。 この変換に関する詳細については、§10.7.3 を参照してください。
例: 次のプログラムは、ラムダ式を実行可能コードと式ツリーの両方として表します。
Func<int,int>
への変換が存在するため、Expression<Func<int,int>>
への変換も存在します。Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data
これらの割り当てに続いて、デリゲート
del
はx + 1
を返すメソッドを参照し、式ツリー exp は式x => x + 1
を記述するデータ構造を参照します。終了サンプル
Expression<TDelegate>
は、インスタンス メソッド Compile
を指定します。これはデリゲート型 TDelegate
を生成します。
Func<int,int> del2 = exp.Compile();
このデリゲートを呼び出すことで、式ツリーで表されるコードが実行されます。 したがって、上記の定義によれば、del
と del2
は同等であり、次の 2 つのステートメントは同じ効果を持ちます。
int i1 = del(1);
int i2 = del2(1);
このコードを実行後、i1
と i2
は両方とも、値 2
を持ちます。
Expression<TDelegate>
が指定する API サーフェスは、上記の Compile
メソッドの要件を超えた処理系定義です。
注: 式ツリーに提供される API の詳細は処理系定義ですが、実装は次のように想定されます。
- ラムダ式からの変換の結果として作成された式ツリーの構造をコードが検査して応答できるようにします。
- ユーザーコード内でプログラム的に式ツリーを作成できるようにする
注釈
8.7 動的型
型 dynamic
は、他のすべての型で使用される静的バインディングとは対照的に、§12.3.2 で詳細に説明されている動的バインディングを使用します。
型 dynamic
は、次の点を除いて object
と同一であると見なされます。
- 型
dynamic
の式に対する演算は動的にバインドできます (§12.3.3)。 - 両方が候補である場合、型推論 (§12.6.3) は、
dynamic
よりobject
を優先します。 -
dynamic
は、以下として使用できません。- object_creation_expression の型 (§12.8.17.2)
- class_base (§15.2.4)
- member_access の predefined_type (§12.8.7.1)
-
typeof
演算子のオペランド - 属性引数
- 制約
- 拡張メソッド型
- struct_interfaces (§16.2.5) または interface_type_list 内の型引数の一部 (§15.2.4.1)。
この同等性のため、次のことが成り立ちます。
- 暗黙的 ID 変換がある
-
object
からdynamic
の間 -
dynamic
をobject
に置き換える際と同じ構築型間 -
dynamic
をobject
に置き換える際と同じタプル型間
-
-
object
との間の暗黙的および明示的な変換は、dynamic
との間でも適用されます。 -
dynamic
をobject
に置き換えたときに同じ署名は、同じ署名とみなされます。 - 実行時には型
dynamic
と型object
は区別できません。 - 型
dynamic
の式は動的式と呼ばれます。
8.8 アンマネージ型
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
unmanaged_type は、reference_type でも type_parameter でもなく、アンマネージドに制約されておらず、型が unmanaged_typeではないインスタンス フィールドを含まない型です。 つまり、unmanaged_type は次のいずれかになります。
-
sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
、float
、double
、decimal
またはbool
。 - すべての enum_type。
- unmanaged_type のインスタンス フィールドのみを含む、ユーザー定義の struct_type。
- アンマネージドであることを制約とする任意の型パラメーター。
- 任意の pointer_type (§23.3)。
8.9 参照型および null 可能性
8.9.1 全般
null 許容参照型は、nullable_type_annotation (?
) を null 非許容参照型に追加することであらわされます。 null 非許容参照型とそれに対応する null 値許容型の間には意味的な違いはなく、どちらもオブジェクトまたは null
への参照になります。
nullable_type_annotation の有無は、式が null 値を許可するかどうかを宣言します。 式がその意図に従って使用されていない場合、コンパイラは診断を提供することがあります。 式の null 状態については、§8.9.5 で定義されています。 null 許容参照型とそれに対応する null 非許容参照型の間には同一性変換が存在します (§10.2.2)。
参照型には、2 つの形式の null 可能性があります。
-
nullable: nullable-reference-type を
null
に割り当てることができます。 デフォルトの null 状態は、maybe-null です。 -
non-nullable: non-nullable reference を
null
値に割り当てることはできません。 デフォルトの null 状態は、not-null です。
注: 型
R
およびR?
は、同じ基になる型R
で表されます。 その基になる型の変数には、オブジェクトへの参照が含まれるか、または「参照なし」を示す値null
になります。 注釈
null 許容参照型とそれに対応する null 非許容参照型の構文上の区別により、コンパイラは診断を生成できます。 コンパイラは、§8.2.1 で定義されている nullable_type_annotation を許可する必要があります。 診断は警告に限定する必要があります。 コンパイル時に生成される診断メッセージの変更を除き、null 許容注釈の有無や null 許容コンテキストの状態によって、プログラムのコンパイル時または実行時の動作が変更することはできません。
8.9.2 null 非許容参照型
null 非許容参照型 は、T
形式の参照型で、ここでは、T
は、型名です。 null 非変数のデフォルトの null 状態は、not-null です。
null でない値が必要な場所でmaybe-null である式が使用されると警告が表示される場合があります。
8.9.3 null 許容参照型
T?
などの 形式 string?
の参照型は、null 許容参照型です。 null 許容変数のデフォルトの null 状態は、maybe null です。 注釈 ?
は、この型が null 許容であることを示します。 コンパイラはこれらの意図を認識して警告を発行できます。 null 許容注釈コンテキストが無効になっている場合、この注釈を使用すると警告が生成されることがあります。
8.9.4 null 許容コンテキスト
8.9.4.1 全般
ソース コードの各行には、null 許容コンテキストがあります。 null 許容コンテキストの注釈と警告フラグは、それぞれ null 許容注釈 (§8.9.4.3) と null 許容警告 (§8.9.4.4) を制御します。 各フラグは、有効または無効です。 コンパイラは、静的フロー分析を使用して、任意の参照変数の null 状態を判断できます。 参照変数の null 状態 (§8.9.5)は、not null、maybe null、maybe default のいずれかとなります。
null 許容コンテキストは、null 許容ディレクティブ (§6.5.9) を介してソース コード内で指定することも、ソース コードの外部にある実装固有のメカニズムを介して指定することもできます。 両方のアプローチを使用する場合、null 許容ディレクティブは外部メカニズムを介して行われた設定を優先します。
null 許容コンテキストのデフォルトの状態は処理系定義です。
この仕様全体を通じて、null 許容ディレクティブが含まれていない、または現在の null 許容コンテキストの状態に関して何も記述されていないすべての C# コードは、注釈と警告の両方が有効になっている null 許容コンテキストを使用してコンパイルされているものと想定されます。
注: 両方のフラグが無効になっている null 許容コンテキストは、参照型の以前の標準動作と一致します。 注釈
8.9.4.2 無効な null 許容
警告フラグと注釈フラグの両方が無効になっている場合、null 許容コンテキストは無効になります。
null 許容コンテキストが無効の場合:
- 注釈のない参照型の変数が
null
で初期化されるか、またはその値が割り当てられる場合、警告は生成されません。 - 参照型の変数が null 値を持つ可能性がある場合、警告は生成されません。
- 参照型
T
の場合、?
の注釈T?
は、メッセージを生成し、型T?
は、T
と同じになります。 - 任意の型パラメータ制約
where T : C?
の場合、?
の注釈C?
は、メッセージを生成し、C?
は、C
と同じになります。 - 任意の型パラメータ制約
where T : U?
の場合、?
の注釈U?
は、メッセージを生成し、U?
は、U
と同じになります。 - ジェネリック制約
class?
は、警告メッセージを生成します。 型パラメータは、参照型にする必要があります。注: このメッセージは、関連のない null 許容警告設定の状態との混同を避けるため、「警告」ではなく「情報」として特徴付けられています。 注釈
- null 許容演算子
!
(§12.8.9) には効果はありません。
例:
#nullable disable annotations string? s1 = null; // Informational message; ? is ignored string s2 = null; // OK; null initialization of a reference s2 = null; // OK; null assignment to a reference char c1 = s2[1]; // OK; no warning on dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // OK; ! is ignored
終了サンプル
8.9.4.3 null 許容注釈
警告フラグが無効で、注釈フラグが有効になっている場合、null 許容コンテキストは、注釈です。
null 許容コンテキストが注釈の場合:
- 任意の参照型
T
の場合、?
の注釈T?
は、T?
が null 許容型であることを示しますが、注釈が無いT
は、null 非許容型です。 - null 可能性に関連する診断警告は生成されません。
- null 許容演算子
!
(§12.8.9) は、そのオペランドの分析された null 状態と、生成されるコンパイル時の診断警告を変更する場合があります。
例:
#nullable disable warnings #nullable enable annotations string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; warnings are disabled s2 = null; // OK; warnings are disabled char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException c1 = s2![1]; // No warnings
終了サンプル
8.9.4.4 null 許容の警告
警告フラグが有効で、注釈フラグが無効になっている場合、null 許容コンテキストは、警告です。
null 許容コンテキストが、警告の場合、コンパイラは、次のケースで診断を生成します。
- maybe null であると判断された参照変数が逆参照されます。
- null 非許容型の参照変数は、maybe null の式に割り当てられます。
-
?
は、null 許容参照型であることを示すために使用されます。 - null 許容演算子
!
(§12.8.9) を使用すると、そのオペランドの null 状態を not null に設定できます。
例:
#nullable disable annotations #nullable enable warnings string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; null-state of s2 is "maybe null" s2 = null; // OK; null-state of s2 is "maybe null" char c1 = s2[1]; // Warning; dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // The warning is suppressed
終了サンプル
8.9.4.5 有効な null 許容
警告フラグと注釈フラグの両方が有効な場合、null 許容コンテキストは有効になります。
null 許容コンテキストが有効の場合:
- 任意の参照型
T
の場合、?
の注釈T?
は、T?
が null 許容型にしますが、注釈が無いT
は、null 非許容型です。 - コンパイラは、静的フロー分析を使用して、任意の参照変数の null 状態を判断できます。 null 許容警告が有効である場合、参照変数の null 状態 (§8.9.5) は、not null、maybe null、maybe default のいずれかになります。
- null 許容演算子
!
(§12.8.9) は、そのオペランドの null 状態を not null に設定します。 - 型パラメーターの null 許容が対応する型引数の null 許容値と一致しない場合、コンパイラは警告を発行できます。
8.9.5 null 可能性と null 状態
8.9.5.1 全般
コンパイラは静的分析を実行する必要はなく、null 可能性に関連する診断警告を生成する必要もありません。
このサブ句の残りの部分は条件付き規範です。
8.9.5.2 フロー分析
診断警告を生成するコンパイラはこれらの規則に準拠します。
すべての式には、次の 3 つの null 状態があります。
- maybe null: 式の値は null と評価される場合があります。
- maybe default: 式の値は、その型のデフォルト値に評価される場合があります。
- not null: 式の値は null ではありません。
式のデフォルトの null 状態は、その型と、宣言時の注釈フラグの状態によって決まります。
- null 許容参照型のデフォルト null 状態は、
- 注釈フラグが有効になっているテキスト内で宣言されている場合は、Maybe null です。
- 注釈フラグが無効になっているテキスト内で宣言されている場合は、Not null です。
- null 非許容参照型のデフォルト null 状態は、not null です。
注:maybe default 状態は、型が
string
などの null 非許容型であり、式default(T)
が null 値である場合に、制約のない型パラメータで使用されます。 null は null 非許容型のドメイン内にないため、状態はおそらくデフォルトです。 注釈
注釈フラグが有効になっているテキストでその変数が宣言されているときに、null 非許容参照型の変数 (§9.2.1) が初期化されるか、maybe null の式に割り当てられると、診断が生成されます。
例: パラメータが null 許容であり、その値が null 非許容型に割り当てられている次のメソッドを検討します。
#nullable enable public class C { public void M(string? p) { // Warning: Assignment of maybe null value to non-nullable variable string s = p; } }
コンパイラは、null である可能性があるパラメーターが null ではない変数に割り当てられる警告を発行することがあります。 割り当て前にパラメーターが null チェックされている場合、コンパイラは null 許容状態分析でパラメーターを使用し、警告を発行しない可能性があります。
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // No warning // Use s } } }
終了サンプル
コンパイラは、分析の一環として変数の null 状態を更新できます。
例: コンパイラは、プログラム内のステートメントに基づいて状態を更新することを選択できます。
#nullable enable public void M(string? p) { int length = p.Length; // Warning: p is maybe null string s = p; // No warning. p is not null if (s != null) { int l2 = s.Length; // No warning. s is not null } int l3 = s.Length; // Warning. s is maybe null }
前の例では、ステートメントの
int length = p.Length;
後に、p
の null 状態が null でないとコンパイラが判断する場合があります。 null だった場合、そのステートメントはNullReferenceException
をスローします。 これは、コードの前にif (p == null) throw NullReferenceException();
があった場合の動作と似ていますが、記述されたコードによって警告が生成される可能性がある点が異なります。その目的は、例外が暗黙的にスローされる可能性があることを警告することです。 終了サンプル
メソッドの後半では、コードは s
が null 参照ではないことを確認します。
s
の null 状態は、null チェック ブロックがクローズした後に maybe null に変更できます。 コンパイラは、s
が null であった可能性があることを前提としてコードが記述されているため、null である可能性があることを推測できます。 一般に、コードに null チェックが含まれている場合、コンパイラは値が null であった可能性があることを推測できます。
例: 次の各式には、何らかの形式の null チェックが含まれています。
o
の null 状態は、次の各ステートメントの後に null 以外から null に変更される可能性があります。#nullable enable public void M(string s) { int length = s.Length; // No warning. s is not null _ = s == null; // Null check by testing equality. The null state of s is maybe null length = s.Length; // Warning, and changes the null state of s to not null _ = s?.Length; // The ?. is a null check and changes the null state of s to maybe null if (s.Length > 4) // Warning. Changes null state of s to not null { _ = s?[4]; // ?[] is a null check and changes the null state of s to maybe null _ = s.Length; // Warning. s is maybe null } }
自動プロパティとフィールドに似たイベント宣言の両方で、コンパイラによって生成されたバッキング フィールドが使用されます。 Null 状態分析では、イベントまたはプロパティへの割り当てがコンパイラによって生成されたバッキング フィールドへの割り当てであると推測される場合があります。
例: コンパイラは、自動プロパティまたはフィールドに似たイベントを記述すると、対応するコンパイラによって生成されたバッキング フィールドを書き込むかどうかを判断できます。 プロパティの null 状態は、バッキング フィールドの状態と一致します。
class Test { public string P { get; set; } public Test() {} // Warning. "P" not set to a non-null value. static void Main() { var t = new Test(); int len = t.P.Length; // No warning. Null state is not null. } }
前の例では、コンストラクターは
P
を null 以外の値に設定せず、コンパイラが警告を発行する可能性があります。 プロパティの型が null 非許容参照型であるため、P
プロパティにアクセスしても警告はありません。 終了サンプル
コンパイラは、プロパティ (§15.7) を状態を持つ変数として、または独立した get アクセサーおよび set アクセサー (§15.7.3) として扱うことができます。
例: コンパイラは、プロパティへの書き込みがプロパティの読み取りの null 状態を変更するか、プロパティを読み取る場合にそのプロパティの null 状態を変更するかを選択できます。
class Test { private string? _field; public string? DisappearingProperty { get { string tmp = _field; _field = null; return tmp; } set { _field = value; } } static void Main() { var t = new Test(); if (t.DisappearingProperty != null) { int len = t.DisappearingProperty.Length; // No warning. A compiler can assume property is stateful } } }
前述の例では、
DisappearingProperty
のバッキング フィールドは、読み取り時に null に設定されます。 ただし、コンパイラは、プロパティを読み取ってもその式の null 状態は変更されないと想定する場合があります。 終了サンプル
コンパイラは、変数、プロパティ、またはイベントを逆参照する任意の式を使用して、null 状態を null 以外に設定できます。 null の場合、逆参照式は NullReferenceException
をスローします。
例:
public class C { private C? child; public void M() { _ = child.child.child; // Warning. Dereference possible null value var greatGrandChild = child.child.child; // No warning. } }
終了サンプル
8.9.5.3 型変換
診断警告を生成するコンパイラはこれらの規則に準拠します。
手記: 型の最上位レベルまたは入れ子になった null 許容注釈の違いは、null 非許容参照型とそれに対応する null 許容型 (§8.9.1) の間にセマンティックな違いがないため、型間の変換が許可されるかどうかには影響しません。 注釈
コンパイラは、変換が縮小されるときに、最上位レベルまたは入れ子になった 2 つの型の間で null 許容注釈が異なる場合に警告を発行することがあります。
例: 最上位レベルの注釈が異なる型
#nullable enable public class C { public void M1(string p) { _ = (string?)p; // No warning, widening } public void M2(string? p) { _ = (string)p; // Warning, narrowing _ = (string)p!; // No warning, suppressed } }
終了サンプル
例: 入れ子になった null 許容注釈が異なる型
#nullable enable public class C { public void M1((string, string) p) { _ = ((string?, string?))p; // No warning, widening } public void M2((string?, string?) p) { _ = ((string, string))p; // Warning, narrowing _ = ((string, string))p!; // No warning, suppressed } }
終了サンプル
コンパイラは、型変換に対して警告を発行するかどうかを判断する際に、インターフェイス分散 (§18.2.3.3)、デリゲート分散 (§20.4)、配列共分散 (§17.6) の規則に従うことができます。
#nullable enable public class C { public void M1(IEnumerable<string> p) { IEnumerable<string?> v1 = p; // No warning } public void M2(IEnumerable<string?> p) { IEnumerable<string> v1 = p; // Warning IEnumerable<string> v2 = p!; // No warning } public void M3(Action<string?> p) { Action<string> v1 = p; // No warning } public void M4(Action<string> p) { Action<string?> v1 = p; // Warning Action<string?> v2 = p!; // No warning } public void M5(string[] p) { string?[] v1 = p; // No warning } public void M6(string?[] p) { string[] v1 = p; // Warning string[] v2 = p!; // No warning } }
終了サンプル
バリアント変換を許可しない型で null 値の許容がどちらの方向にも異なる場合、コンパイラは警告を発行することがあります。
#nullable enable public class C { public void M1(List<string> p) { List<string?> v1 = p; // Warning List<string?> v2 = p!; // No warning } public void M2(List<string?> p) { List<string> v1 = p; // Warning List<string> v2 = p!; // No warning } }
終了サンプル
条件付き規範テキストの末尾
ECMA C# draft specification