8 種類
8.1 全般
C# 言語の型は、参照型値型の 2 つの主要なカテゴリに分かれています。 値型と参照型はどちらも ジェネリック型で、1 つ以上の 型パラメーターを受け取ります。 型パラメーターは、値型と参照型の両方を指定できます。
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) は、安全でないコード (§23) でのみ使用できます。
値型は参照型とは異なり、値型の変数にはデータが直接含まれますが、参照型の変数はデータに 参照 を格納し、後者は objects と呼ばれます。 参照型では、2 つの変数が同じオブジェクトを参照できるため、1 つの変数に対する操作が、もう一方の変数によって参照されるオブジェクトに影響を与える可能性があります。 値型では、変数にはそれぞれデータのコピーがあり、一方に対する操作が他方に影響を与えることはできません。
注: 変数が参照パラメーターまたは出力パラメーターである場合は、独自のストレージはありませんが、別の変数のストレージを参照します。 この場合、ref 変数または out 変数は、実質的に別の変数のエイリアスであり、個別の変数ではありません。 end note
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 で詳しく説明します。
参照型の値は、型の instance への参照であり、後者はオブジェクトと呼ばれます。 null
特別な値は、すべての参照型と互換性があり、インスタンスがないことを示します。
8.2.2 クラス型
クラス型は、 data メンバー (定数とフィールド)、 関数メンバー (メソッド、プロパティ、イベント、インデクサー、演算子、インスタンス コンストラクター、ファイナライザー、静的コンストラクター)、入れ子になった型を含むデータ構造を定義します。 クラス型は継承をサポートします。これは、派生クラスが基底クラスを拡張して特殊化できるメカニズムです。 クラス型のインスタンスは、 object_creation_expression (§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 動的な型
object
と同様に、dynamic
型は任意のオブジェクトを参照できます。 dynamic
型の式に操作を適用すると、その解決はプログラムが実行されるまで遅延されます。 したがって、参照先オブジェクトに操作を正当に適用できない場合、コンパイル中にエラーは発生しません。 代わりに、実行時に操作の解決が失敗すると、例外がスローされます。
dynamic
型については、§8.7 で詳しく説明し、§12.3.1 では動的バインディング。
8.2.5 文字列型
string
型は、object
から直接継承するシールクラス型です。 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++ のデリゲートに最も近いのは関数ポインターですが、関数ポインターは静的関数のみを参照できますが、デリゲートは静的メソッドとインスタンス メソッドの両方を参照できます。 後者の場合、デリゲートはメソッドのエントリ ポイントへの参照だけでなく、メソッドを呼び出すオブジェクト インスタンスへの参照も格納します。 end note
デリゲート型については、 §20 で説明します。
8.3 値型
8.3.1 全般
値型は、構造体型または列挙型です。 C# には、 simple 型と呼ばれる定義済みの構造体型のセットが用意されています。 単純型はキーワードを使用して識別されます。
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 許容値型 (§8.3.12) である場合にのみnull
値を含めることができます。 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 既定のコンストラクター
すべての値型は、 default コンストラクターと呼ばれる、パラメーターなしのパブリック インスタンス コンストラクターを暗黙的に宣言します。 既定のコンストラクターは、値型の default 値 と呼ばれるゼロ初期化インスタンスを返します。
- すべての simple_typeの既定値は、すべてのゼロのビット パターンによって生成される値です。
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) は、既定のコンストラクターを使用する場合と同じ結果を生成します。 end note
例: 次のコードでは、変数
i
、j
、k
はすべて 0 に初期化されます。class A { void F() { int i = 0; int j = new int(); int k = default(int); } }
end の例
すべての値型には暗黙的にパブリック パラメーターなしのインスタンス コンストラクターがあるため、構造体型にパラメーターなしのコンストラクターの明示的な宣言を含めさせることはできません。 ただし、構造体型は、パラメーター化されたインスタンス コンストラクター (§16.4.9) を宣言できます。
8.3.4 構造体の種類
構造体型は、定数、フィールド、メソッド、プロパティ、イベント、インデクサー、演算子、インスタンス コンストラクター、静的コンストラクター、入れ子になった型を宣言できる値型です。 構造体型の宣言については、 §16 で説明します。
8.3.5 単純型
C# には、単純型と呼ばれる定義済みの struct
型のセットが用意されています。 単純型はキーワードを使用して識別されますが、これらのキーワードは、次の表に示すように、System
名前空間の定義済みのstruct
型のエイリアスです。
キーワード | エイリアス化された型 |
---|---|
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
end の例
注: 単純型は、特定の追加操作を許可するという点で、他の構造体型とは異なります。
- ほとんどの単純型では、 literals (§6.4.5) を記述して値を作成できますが、C# では一般的に構造体型のリテラルのプロビジョニングは行われません。 例:
123
はint
型のリテラルであり、'a'
はchar
型のリテラルです。 end の例- 式のオペランドがすべて単純型定数である場合、コンパイラはコンパイル時に式を評価できます。 このような式は、 constant_expression (§12.23) と呼ばれます。 他の構造体型によって定義された演算子を含む式は、定数式とは見なされません
const
宣言を使用して、単純型 (§15.4) の定数を宣言できます。 他の構造体型の定数を持つことはできませんが、静的な読み取り専用フィールドでも同様の効果が得られます。- 単純型を含む変換は、他の構造体型で定義された変換演算子の評価に参加できますが、ユーザー定義変換演算子は、別のユーザー定義変換演算子 (§10.5.3) の評価には参加できません。
end note。
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
と同じ表現ですが、一方の型で許可されるすべての操作が他方で許可されるわけではありません。 end note
すべての符号付き整数型は、2 の補数形式を使用して表されます。
integral_type単項演算子と 2 項演算子は、§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'
と同じです。 end の例
checked
およびunchecked
演算子とステートメントは、整数型の算術演算と変換のオーバーフロー チェックを制御するために使用されます (§12.8.20)。 checked
コンテキストでは、オーバーフローによってコンパイル時エラーが発生するか、System.OverflowException
がスローされます。 unchecked
コンテキストでは、オーバーフローは無視され、宛先の型に収まらない上位ビットは破棄されます。
8.3.7 浮動小数点型
C# では、 float
と double
の 2 つの浮動小数点型がサポートされています。 float
型とdouble
型は、32 ビットの単精度および 64 ビットの倍精度 IEC 60559 形式を使用して表され、次の値のセットが提供されます。
- 正のゼロと負のゼロ。 ほとんどの状況では、正のゼロと負のゼロは単純な値 0 と同じように動作しますが、特定の操作では 2 つの操作が区別されます (§12.10.3)。
- 正の無限大と負の無限大。 無限大は、ゼロ以外の数字をゼロで割るような演算で生成されます。
例:
1.0 / 0.0
は正の無限大を生成し、–1.0 / 0.0
は負の無限大を生成します。 end の例 - Not-a-Number 値。多くの場合、NaN と省略されます。 NaN は、ゼロをゼロで割るなど、無効な浮動小数点演算で生成されます。
- フォーム m × m × 2e の有限のセット ( は 1 または -1、mおよびeは、特定の浮動小数点型によって決定されます。
float
の場合、 0 <m< 2²⁴ と −149 ≤ e ≤ 104、double
、0 <m< 2⁵ ²、−1075 ≤ e ≤ 970。 非正規化された浮動小数点数は、有効な 0 以外の値と見なされます。 C# は、準拠する実装が非正規化された浮動小数点数をサポートすることを必要とせず、禁止もしません。
float
型は、約 1.5 × 10⁻⁴⁵ から 3.4 × 10 ²⁸ までの範囲の値を 7 桁の精度で表すことができます。
double
型は、約 5.0 × 10⁻²²⁴ ~ 1.7 × 10 ²⁰⁸ の範囲の値を 15 ~ 16 桁の精度で表すことができます。
2 項演算子のいずれかのオペランドが浮動小数点型の場合は、 §12.4.7 で詳しく説明されているように標準の数値昇格が適用され、演算は float
または double
精度で実行されます。
代入演算子を含む浮動小数点演算子は例外を生成しません。 代わりに、次に示すように、例外的な状況では、浮動小数点演算によってゼロ、無限大、または NaN が生成されます。
- 浮動小数点演算の結果は、変換先の形式で最も近い表現可能な値に丸められます。
- 浮動小数点演算の結果の大きさが変換先の形式に対して小さすぎる場合、操作の結果は正の 0 または負の 0 になります。
- 浮動小数点演算の結果の大きさが変換先の形式に対して大きすぎる場合、操作の結果は正の無限大または負の無限大になります。
- 浮動小数点演算が無効な場合、操作の結果は NaN になります。
- 浮動小数点演算のオペランドの一方または両方が NaN の場合、演算の結果は NaN になります。
浮動小数点演算は、演算の結果の種類よりも高い精度で実行できます。 浮動小数点型の値をその型の正確な精度に強制するには、明示的なキャスト (§12.9.7) を使用できます。
例: 一部のハードウェア アーキテクチャでは、
double
型よりも範囲と精度が高い "extended" 浮動小数点型または "long double" 浮動小数点型がサポートされ、この高い精度の型を使用してすべての浮動小数点演算が暗黙的に実行されます。 このようなハードウェア アーキテクチャは、パフォーマンスの過剰なコストでのみ、精度で浮動小数点演算を実行できます。また、パフォーマンスと精度の両方を失う実装を必要とするのではなく、C# では、より高い精度の型をすべての浮動小数点演算に使用できます。 より正確な結果を提供する以外に、測定可能な効果はほとんどありません。 ただし、x * y / z
形式の式では、乗算によってdouble
範囲外の結果が生成されますが、後続の除算では一時的な結果がdouble
の範囲に戻ります。式が高い範囲の形式で評価されると、無限大ではなく有限の結果が生成される可能性があります。 end の例
8.3.8 Decimal 型
decimal
型は 128 ビットのデータ型で、財務や通貨の計算に適しています。 decimal
型は、少なくとも -7.9 × 10⁻²⁸ ~ 7.9 × 10²⁸ の範囲の値を、少なくとも 28 桁の精度で表すことができます。
decimal
型の有限の値のセットは、形式 (-1)v × c × 10⁻e です。ここで、符号 v は 0 または 1 で、係数 c は 0 ≤ c<Cmax によって指定されます スケール e は、Emin ≤ e ≤ Emax であり、Cmax は少なくとも 1 × 10²⁸、Emin ≤ 0、 および Emax ≥ 28. decimal
型は、符号付きゼロ、無限大、または NaN を必ずしもサポートしていません。
decimal
は、10 乗でスケーリングされた整数として表されます。 絶対値が1.0m
未満のdecimal
の場合、値は少なくとも小数点以下 28 桁目に正確です。 1.0m
以上の絶対値を持つdecimal
の場合、値は少なくとも 28 桁に正確です。 float
とdouble
のデータ型とは対照的に、0.1
などの小数部は、10 進表現で正確に表すことができます。 float
表現とdouble
表現では、このような数値は多くの場合、終了しないバイナリ拡張を持ち、これらの表現は丸めエラーを起こしやすくなります。
二項演算子のいずれかのオペランドが decimal
型の場合は、 §12.4.7 で詳しく説明されているように、標準の数値の昇格が適用され演算は double
有効桁数で実行されます。
decimal
型の値に対する演算の結果は、正確な結果を計算し (演算子ごとに定義されているように小数点以下桁数を保持する)、その結果、表現に合わせて丸める結果になります。 結果は最も近い表現可能な値に丸められ、結果が 2 つの表現可能な値に等しく近い場合は、最下位桁の偶数を持つ値に丸められます (これは "銀行家の丸め" と呼ばれます)。 つまり、結果は少なくとも小数点以下 28 桁目に正確です。 丸めでは、0 以外の値から 0 の値が生成される場合があることに注意してください。
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++ 言語では、0 個の整数または浮動小数点値、または null ポインターをブール値
false
に変換でき、0 以外の整数または浮動小数点値、または null 以外のポインターをブール値true
に変換できます。 C# では、このような変換は、整数または浮動小数点値を明示的に 0 に比較するか、オブジェクト参照をnull
と明示的に比較することによって実現されます。 end note
8.3.10 列挙型
列挙型は、名前付き定数を持つ個別の型です。 すべての列挙型には基になる型があり、 byte
、 sbyte
、 short
、 ushort
、 int
、 uint
、 long
、または ulong
になります。 列挙型の値のセットは、基になる型の値のセットと同じです。 列挙型の値は、名前付き定数の値に制限されません。 列挙型は、列挙宣言 (§19.2) によって定義されます。
8.3.11 タプル型
タプル型は、省略可能な名前と個々の型を持つ、順序付けされた固定長の値シーケンスを表します。 タプル型の要素の数は、その 同一性と呼ばれます。 タプル型は n ≥ 2 で (T1 I1, ..., Tn In)
書き込まれます。ここで、識別子 I1...In
は省略可能な 要素名です。
この構文は、System.ValueTuple<...>
からT1...Tn
型で構築された型の短縮形です。これは、2 から 7 までの間の任意のアリティのタプル型を直接表現できるジェネリック構造体型のセットです。
対応する数の型パラメーターを持つタプル型のアリティに直接一致する System.ValueTuple<...>
宣言は存在する必要はありません。 代わりに、arity が 7 より大きいタプルは、ジェネリック構造体型System.ValueTuple<T1, ..., T7, TRest>
で表されます。タプル要素に加えて、別のSystem.ValueTuple<...>
型を使用して、残りの要素の入れ子になった値を含むRest
フィールドがあります。 このような入れ子は、例えば Rest
フィールドの存在を介して、様々な方法で観察することができる。 追加のフィールドが 1 つだけ必要な場合は、 System.ValueTuple<T1>
ジェネリック構造体型が使用されます。この型自体はタプル型とは見なされません。 7 つ以上の追加フィールドが必要な場合は、 System.ValueTuple<T1, ..., T7, TRest>
が再帰的に使用されます。
タプル型内の要素名は個別にする必要があります。 フォーム ItemX
のタプル要素名。 X
はタプル要素の位置を表す0
開始されていない 10 進数のシーケンスであり、 X
で示される位置でのみ許可されます。
省略可能な要素名は、 ValueTuple<...>
型では表されず、タプル値のランタイム表現には格納されません。 ID 変換 (§10.2.2) は、要素型の ID 変換可能なシーケンスを持つタプル間に存在します。
new
演算子 §12.8.17.2 はタプル型構文new (T1, ..., Tn)
では適用できません。 タプル値は、タプル式 (§12.8.6) から作成することも、 new
演算子を ValueTuple<...>
から構築された型に直接適用して作成することもできます。
タプル要素は、 Item1
、 Item2
などの名前を持つパブリック フィールドであり、タプル値 (§12.8.7 のメンバー アクセスを介してアクセスできます。 さらに、タプル型に特定の要素の名前がある場合は、その名前を使用して問題の要素にアクセスできます。
注: 大きなタプルが入れ子になった
System.ValueTuple<...>
値で表されている場合でも、各タプル要素は、その位置に対応するItem...
名で直接アクセスできます。 end note
例: 次の例を考えます。
(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
名と、対応するタプル要素名 (型に存在する場合) によってアクセスできることを示しています。 end の例
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 プロパティにアクセスするプロセスは、 unwrapping と呼ばれます。
既定のコンストラクターに加えて、 T?
のすべての null 許容値型には、 T
型の単一パラメーターを持つパブリック コンストラクターがあります。 T
型の値x
指定すると、フォームのコンストラクター呼び出し
new T?(x)
は、Value
プロパティがx
T?
の null 以外のインスタンスを作成します。 指定された値に対して null 許容値型の null 以外のインスタンスを作成するプロセスは、 wrapping と呼ばれます。
暗黙的な変換は、 null
リテラルから T?
(§10.2.7) に、および T
から T?
(§10.2.6) に使用できます。
null 許容値型 T?
はインターフェイスを実装しません (§18)。 特に、これは、基になる型 T
が行うインターフェイスを実装していないことを意味します。
8.3.13 ボックス化とボックス化解除
ボックス化とボックス化解除の概念は、型object
との間で変換されるvalue_typeの値を許可することで、value_typeとreference_typeの間のブリッジを提供します。 ボックス化とボックス化解除により、任意の型の値を最終的に 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 } }
end の例
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 }
end の例
列挙型以外の構築型は、 unmanaged_type として使用しないでください (§8.8)。
8.4.2 型引数
型引数リスト内の各引数は、単に 型です。
type_argument_list
: '<' type_arguments '>'
;
type_arguments
: type_argument (',' type_argument)*
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
各型引数は、対応する型パラメーター (§15.2.5) の制約を満たす必要があります。 null 許容が型パラメーターの null 許容値と一致しない参照型引数が制約を満たします。ただし、警告が発行される可能性があります。
8.4.3 オープンタイプとクローズタイプ
すべての型は、 open 型 または 閉じ型として分類できます。 オープン型は、型パラメーターを含む型です。 具体的には次のとおりです。
- 型パラメーターは、開いている型を定義します。
- 配列型は、その要素型がオープン型の場合にのみ、開いている型です。
- 構築された型は、その型引数の 1 つ以上が開いている型の場合にのみ、開いている型です。 構築された入れ子になった型は、1 つ以上の型引数またはその含む型の型引数が開いている型の場合にのみ、開いている型です。
閉じた型は、開いている型ではない型です。
実行時に、ジェネリック型宣言内のすべてのコードは、ジェネリック宣言に型引数を適用することによって作成された、閉じた構築型のコンテキストで実行されます。 ジェネリック型内の各型パラメーターは、特定のランタイム型にバインドされます。 すべてのステートメントと式の実行時処理は常に閉じた型で行われ、開いている型はコンパイル時の処理中にのみ発生します。
同じ非連結ジェネリック型から構築され、対応する型引数の間に ID 変換が存在する場合、2 つの閉じた構築型は ID 変換可能 (§10.2.2) です。 対応する型引数自体は、ID 変換可能な構築型またはタプルを閉じてもかまいません。 ID 変換可能な閉じた構築型は、静的変数の 1 つのセットを共有します。 それ以外の場合、閉じた構築された各型には、独自の静的変数のセットがあります。 オープン型は実行時に存在しないため、オープン型に関連付けられた静的変数はありません。
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
は、この制約を満たす参照型です。 end noteA
は、参照型 (§8.2) であることが知られている型パラメーターです。
- 制約が値型制約 (
struct
) の場合、A
型は次のいずれかを満たす必要があります。A
はstruct
型またはenum
型ですが、null 許容値型ではありません。
注:
System.ValueType
とSystem.Enum
は、この制約を満たさない参照型です。 end noteA
は、値型制約 (§15.2.5) を持つ型パラメーターです。
- 制約がコンストラクター制約
new()
場合、A
型はabstract
されず、パラメーターなしのパブリック コンストラクターを持つ必要があります。 これは、次のいずれかが当てはまる場合に満たされます。
指定された型引数で 1 つ以上の型パラメーターの制約が満たされていない場合、コンパイル時エラーが発生します。
型パラメーターは継承されないため、制約も継承されません。
例: 次の
D
では、基本class
B<T>
によって課される制約をT
が満たすように、型パラメーターT
に制約を指定する必要があります。 これに対し、List<T>
はT
のIEnumerable
を実装するため、class
E
は制約を指定する必要はありません。class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
end の例
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) を使用できます。 さらに、型パラメーターによって指定された型を持つ値 can 型パラメーターに値型制約がない限り、==
と!=
(§12.12.7) を使用して null と比較できます。new
式 (§12.8.17.2) は、型パラメーターがconstructor_constraintまたは値型制約 (§15.2.5) によって制約されている場合にのみ、型パラメーターと共に使用できます。- 型パラメーターは、属性内のどこにも使用できません。
- メンバー アクセス (§12.8.7) または型名 (§7.8) で型パラメーターを使用して、静的メンバーまたは入れ子になった型を識別することはできません。
- 型パラメーターを unmanaged_type として使用することはできません (§8.8)。
end note
型として、型パラメーターは純粋にコンパイル時のコンストラクトです。 実行時に、各型パラメーターは、ジェネリック型宣言に型引数を指定することによって指定されたランタイム型にバインドされます。 したがって、型パラメーターで宣言された変数の型は、実行時に、§8.4.3 §8.4.3 で構築された閉じた型になります。 型パラメーターを含むすべてのステートメントと式の実行時実行では、そのパラメーターの型引数として指定された型が使用されます。
8.6 式ツリーの種類
式ツリー ラムダ式を実行可能コードの代わりにデータ構造として表現できます。 式ツリーは、フォーム System.Linq.Expressions.Expression<TDelegate>
の式ツリー型値です。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
を記述するデータ構造を参照します。end の例
Expression<TDelegate>
は、TDelegate
型のデリゲートを生成するインスタンス メソッドCompile
を提供します。
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 の詳細は実装で定義されていますが、実装では次のことが想定されます。
- ラムダ式からの変換の結果として作成された式ツリーの構造を調べて応答するコードを有効にする
- ユーザー コード内で式ツリーをプログラムで作成できるようにする
end note
8.7 動的な型
dynamic
型は、他のすべての型で使用される静的バインディングではなく、§12.3.2 で詳しく説明されているように、動的バインディングを使用します。
dynamic
型は、次の点を除き、object
と同じと見なされます。
dynamic
型の式に対する操作は動的にバインドできます (§12.3.3)。- 型推論 (§12.6.3) は、両方が候補の場合、
object
よりもdynamic
を優先します。 dynamic
は次のように使用できません。- object_creation_expressionの型 (§12.8.17.2)
- a 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。
- 安全でないコード (§23.2) では、任意の pointer_type (§23.3)。
8.9 参照型と null 値の許容
8.9.1 全般
null 許容 参照型 は、null 非許容参照型に nullable_type_annotation (?
) を追加することによって示されます。 null 非許容参照型とそれに対応する null 許容型の間にはセマンティックな違いはありません。どちらもオブジェクトへの参照でも null
でもかまいません。 nullable_type_annotationの有無によって、式が null 値を許可するかどうかを宣言します。 コンパイラは、その意図に従って式が使用されていない場合に診断を提供する場合があります。 式の null 状態は、 §8.9.5 で定義されます。 NULL 許容参照型とそれに対応する null 非許容参照型 (§10.2.2) の間に ID 変換が存在します。
参照型には、次の 2 つの形式の null 許容があります。
- nullable: nullable-reference-type を
null
割り当てることができます。 既定の null 状態は maybe-null です。 - null 非許容: 非 null 許容参照 には
null
値を割り当ててはいけません。 既定の null 状態は not-null です。
注:
R
型とR?
型は、同じ基になる型 (R
) で表されます。 その基になる型の変数には、オブジェクトへの参照を含めるか、または "参照なし" を示すnull
値を指定できます。 end note
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 状態は null です。 注釈 ?
は、この型の変数が null 許容であることを示します。 コンパイラはこれらの意図を認識して警告を発行できます。 null 許容注釈コンテキストが無効になっている場合、この注釈を使用すると警告が生成される可能性があります。
8.9.4 Null 許容コンテキスト
8.9.4.1 全般
ソース コードのすべての行には、 null 許容コンテキストがあります。 null 許容コンテキストの注釈と警告フラグは、それぞれ null 許容注釈 (§8.9.4.3) と null 許容警告 (§8.9.4.4) を制御します。 各フラグは、 可能 または disabledできます。 コンパイラは、静的フロー分析を使用して、任意の参照変数の null 状態を判断できます。 参照変数の null 状態 (§8.9.5) は、 null、 maybe null、または maybe の既定値です。
null 許容コンテキストは、null 許容ディレクティブ (§6.5.9) またはソース コードの外部にある実装固有のメカニズムを使用して、ソース コード内で指定できます。 両方の方法を使用する場合、null 許容ディレクティブは、外部メカニズムを介して行われた設定よりも優先されます。
null 許容コンテキストの既定の状態は実装定義です。
この仕様全体を通じて、null 許容ディレクティブを含まない、または現在の null 許容コンテキストの状態に関するステートメントが作成されていない C# コードはすべて、注釈と警告の両方が有効になっている null 許容コンテキストを使用してコンパイルされたものと見なされます。
注: 両方のフラグが無効になっている null 許容コンテキストは、参照型の以前の標準動作と一致します。 end note
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 許容警告設定の状態と混同しないように、"警告" ではなく "情報" として特徴付けられます。これは関係ありません。 end note
- 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
end の例
8.9.4.3 Null 許容注釈
警告フラグが無効で注釈フラグが有効になっている場合、null 許容コンテキストは 注釈。
null 許容コンテキストが の場合は:
- 参照型
T
の場合、T?
の注釈?
は、null 許容型T?
が示されますが、未示の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
end の例
8.9.4.4 Null 許容警告
警告フラグが有効で注釈フラグが無効になっている場合、null 許容コンテキストは 警告。
null 許容コンテキストが 警告場合、コンパイラは次の場合に診断を生成できます。
- null がであると判断された参照変数は逆参照されます。
- null 非許容型の参照変数は、null 値される式に割り当てられます。
?
は、null 許容参照型をメモするために使用されます。- null を許容する演算子
!
(§12.8.9) を使用して、オペランドの null 状態を 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
end の例
8.9.4.5 Null 許容有効化
警告フラグと注釈フラグの両方が有効になっている場合、null 許容コンテキストは 有効になります。
null 許容コンテキストが有効 場合:
- 参照型
T
の場合、T?
の注釈?
は null 許容型T?
しますが、注釈のないT
は null 非許容です。 - コンパイラは、静的フロー分析を使用して、任意の参照変数の null 状態を判断できます。 null 許容警告が有効な場合、参照変数の null 状態 (§8.9.5) は、 null、 null、または既定maybe のいずれかになります
- null 許容演算子
!
(§12.8.9) は、オペランドの null 状態を null 設定。 - 型パラメーターの null 許容が対応する型引数の null 許容値と一致しない場合、コンパイラは警告を発行できます。
8.9.5 Nullabilities と null 状態
コンパイラは静的分析を実行する必要も、null 許容に関連する診断警告を生成する必要もありません。
このサブクラウスの残りの部分は条件付きで規範的です。
診断警告を生成するコンパイラは、これらの規則に準拠しています。
すべての式には、次の 3 つの null 状態があります。
- おそらく null: 式の値は null に評価される可能性があります。
- おそらく default: 式の値は、その型の既定値に評価される可能性があります。
- not null: 式の値は null ではありません。
式の 既定の null 状態 は、その型と、宣言時の注釈フラグの状態によって決まります。
- null 許容参照型の既定の null 状態は次のとおりです。
- 注釈フラグが有効になっているテキスト内で宣言が行われる場合、null になる可能性があります。
- 注釈フラグが無効になっているテキスト内に宣言がある場合は null ではありません。
- null 非許容参照型の既定の null 状態は null ではありません。
注: maybe の既定値 状態は、型が null 非許容型 (
string
など) で、式default(T)
が null 値である場合に、制約のない型パラメーターで使用されます。 null 非許容型のドメインに null が含まれていないため、状態は既定である可能性があります。 end note
診断は、null 非許容参照型の変数 (§9.2.1) が初期化されるか、注釈フラグが有効になっているテキストで変数が宣言されている場合に null になる可能性がある式に割り当てられたときに生成できます。
例: パラメーターが null 許容で、その値が null 非許容型に割り当てられる次のメソッドについて考えてみましょう。
#nullable enable public class C { public void M(string? p) { // 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; // Use s } } }
end の例
コンパイラは、分析の一環として変数の null 状態を更新できます。
例: コンパイラは、プログラム内のステートメントに基づいて状態を更新することを選択できます。
#nullable enable public void M(string? p) { // p is maybe-null int length = p.Length; // p is not null. string s = p; if (s != null) { int l2 = s.Length; } // s is maybe null int l3 = s.Length; }
前の例では、ステートメントの
int length = p.Length;
後に、p
の null 状態が null でないとコンパイラが判断する場合があります。 null の場合、そのステートメントはNullReferenceException
をスローします。 これは、コードの前にif (p == null) throw NullReferenceException();
が付いていた場合の動作に似ていますが、書き込まれたコードが警告を生成する可能性がある点が異なります。その目的は、例外が暗黙的にスローされる可能性があることを警告することです。
このメソッドの後半で、コードは s
が null 参照ではないことを確認します。 s
の null 状態は、null チェック ブロックが閉じた後に null に変わる可能性があります。 コンパイラは、 s
が null であった可能性があることを前提としてコードが記述されたため、null である可能性があることを推測できます。 一般に、コードに null チェックが含まれている場合、コンパイラは値が null であった可能性があることを推測end の例
例: コンパイラは、プロパティ (§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; } } }
前の例では、
DisappearingProperty
のバッキング フィールドは読み取り時に null に設定されています。 ただし、コンパイラでは、プロパティを読み取っても、その式の null 状態は変更されないと想定される場合があります。 end の例
条件付きで規範的なテキストの末尾
ECMA C# draft specification