Movatterモバイル変換


[0]ホーム

URL:


Logo

目次

キーワード

概要

通常、「値型」はnull 値(無効な値)を取れません。ところが、データベース等、一部のアプリケーションでは、値型の通常の(有効な)値とnull(無効な値)を取るような型が欲しいときがあります。そこで、C# 2.0 では、null 許容型(Nullable 型)という特殊な型が用意されました。

Ver. 8.0

C# 8.0 では、参照型についても? の有無で null の可否を指定する機能が追加されました。この機能を指してnull 許容参照型(nullable reference type)と言ったりします。

この null 許容参照型と区別する意味で、本項で説明している機能(C# 2.0 時代には唯一の null 許容型だった)を指して、null 許容値型(nullable value type)と呼ぶこともあります。

ポイント
  • 値型 T に対して、T? をいう書き方で null 許容型になります。

  • null 許容型は、元となる値型の値かnull を保持できる型です。

null 許容型

null 許容型(nullable type)は、値型の型名の後ろに? を付ける事で、元の型の値またはnull の値を取れる型になるというものです。int 型で例に取ると、以下のような書き方が出来ます。

int? x = 123;int? y =null;

null 非許容型

(本項の意味、すなわち null 許容値型の場合)null 許容型にできるのは「null 許容型を除く値型」のみです。

要するに、int?? のように、「多重に null 許容」な型は作れないということです。int?? と書くとコンパイル エラーになります。

C#の仕様書上は、この「null 許容型を除く値型」を指して、null 非許容型(non-nullable type)と言ったりもします。日本語の場合は「null 非許容」よりも「非 null」とか書く方がわかりやすいかもしれません。

C# 8.0 以降では「null 非許容値型」や「非 null 値型」というように、値型であることを強調する呼び方もします。

null 許容参照型

C# 7.3 以前では、string? というのは定義できません(参照型には? を付けれない)。

C# 8.0 で、null 許容参照型と呼ばれる新しい機能が入って参照型でも? の有無で null の可否を指定できるようになりました。ただ、「後入り」な機能なので、本項で説明している null 許容値型とは少し挙動が違ったりします。

詳しくは別項で説明予定です。

null 許容型のメンバー

T? という書き方で得られる null 許容型は、コンパイル結果的には、Nullable<T>構造体(System名前空間) と等価になります。例えば、以下の2つの変数x と y は全く同じ型の変数になります。

int? x;Nullable<int> y;

ちなみに、リフレクションで型情報を取り出そうとした場合、null許容型はNullable<T>構造体に見えます。

そして、このNullable<T>構造体は、HasValueというbool型のプロパティと、ValueというT型のプロパティを持っています。

Nullable<T> 型のメンバー
戻り値の型プロパティ名説明
boolHasValue有効な(null でない)値を持っていればtrue、 値がnull ならばfalse を返します。
TValue有効な値を返します。 もし、HasValuefalse(値がnull)だった場合、 例外InvalidOperationException 投げます。

また、int? x = 123; という書き方ができることから容易に想像が付くように、T?型 とT 型の間には暗黙の型変換ができます。TT? の変換は常に可能で、以下のようなコードの下2行は等価になります。

int? x;x = 123;x =new int?(123);// x = 123; と等価。

その逆、T?T の変換は、HasValuetrue のときのみ可能で、HasValuefalse の時にはInvalidOperationException がスローされます。

int? x = 123;int? y =null;int z;z = (int)x;// OK。z = (int)y;// 例外が発生。

null 許容型に対する演算

元となる型T が持っている演算子は、そのまま null 許容型T? に対して利用できます。

Nullable<T> 型に対する演算
単項演算+ ++ - -- ! ~オペランドも計算結果も共にT型の単項演算子がある場合、T?に対してもその演算子を利用できます。T?型のオペランドが null の場合、計算結果も null になります。
二項演算+ - * / % & | ^(左右両方の)オペランドも計算結果も共にT型の二項演算子がある場合、T?に対してもその演算子を利用できます。T?型のオペランドのどちらか片方でも null だった場合、計算結果も null になります。 (ただし、bool 型に対する&および|は例外で、 これらに関しては後述します。)
シフト演算<< >>これらも二項演算と同様で、T型の演算子がある場合、T?に対してもその演算子を利用できます。 ただし、シフト演算ですので、右オペランドは int 型です。T?型の左オペランドが null だった場合、計算結果も null になります。
等値演算== !=T型の等値演算がある場合、T?型の等値判定も可能です。T?型の オペランドが左右とも null の場合、比較結果は等しいと判定されます。 また、有効な(non-null の)値と null は等しくありません。 左右ともに有効な値の場合、T型の比較結果と同じになります。
関係演算< > <= >=T型の比較演算がある場合、T?型の比較も可能です。T?型のオペランドのどちらか片方でも null だった場合、計算結果は false になります。 左右ともに有効な値の場合、T型の比較結果と同じになります。

bool? 型に対する& および| は以下のような結果になります。

bool? に対する &、|
xyx & yx | y
truetruetruetrue
truefalsefalsetrue
truenullnulltrue
falsetruefalsetrue
falsefalsefalsefalse
falsenullfalsenull
nulltruenulltrue
nullfalsefalsenull
nullnullnullnull

null 合体演算子 (??)

null 許容型には、?? 演算という特殊な演算子を使えます。この??演算子はnull合体演算子と呼ばれ、値がnull かどうかを判別し、null の場合には別の値を割り当てる演算子です。

// x, y は int? 型の変数int? z = x ?? y;// x != null ? x : yint i = z ?? -1;// z != null ? z.Value : -1

coalesce

null合体演算子は、英語では null coalescing operator と言います。

coalesceという名前はSQLの同様の機能から来ているようです。SQLでも、「もし値がnullだったら、別の有効な値を返す」という機能を持ったCOALESCE関数というものがあります。

coalesceの元の英単語の意味は、合体・融合・癒着というような意味です。null coalescing operatorやCOALESCE関数の意味としては、「癒着」が一番近い気がします。SQLが由来ですので、歯抜け(テーブル中のnullの行 = 値が欠けている状態)をパテで埋めるようなイメージでしょうか。

null 合体代入 (??=)

C# 8.0 では、null合体演算子 (??)も複合代入に使えるようになりました(??=)。

例えば以下のような書き方ができます。

staticvoid M(string s =null){    s??="default string";Console.WriteLine(s);}

意味としては、if (s == null) s = ...; と同じになります。キャッシュ用途に便利だったりします。

結果の型

C# では、代入や複合代入自体も式になっています。なので、var z = y += x; みたいな感じでつないで掛けて、var z = (y += x); という意味で評価されます。この時、ほとんどの場合、y += x の部分の結果の型はy の型になります。

bytex = 1;bytey = 2;varz = (y +=x);// こう書くと y が byte なので z も byte に。varw =y +x;// この場合は int だったりする。C# の int 未満の整数の足し算結果は int になる。

この点に関して、null 合体代入は例外的な挙動をします。というのも、?? の最大の目的は「null だった時に何か有効な値に差し替える」というものなので、結果の型は非 null であってほしい場合がほとんどです。なので、y ??= x の結果の型はy の側ではなく、x の側から推論されます。

#nullableenablestring?s1 =null;strings2 =s1 ??="";// s1 に ? が付いていても、s1 ??= "" の結果は string。int?i1 =null;inti2 =i1 ??= 0;// i1 に ? が付いていても、i1 ??= 0 の結果は int。float?f1 =null;float?f2 =null;float?f3 =f2 ??=f1;// 右辺も null 許容なら結果の方も null 許容。

キャッシュ用途で以下のような書き方をよくするため、こういう型決定ルールになっていないと使いにくくなります。

publicT Property => _cache ??=GetValue();privateT? _cache;privateTGetValue(){// 計算に時間がかかる処理}

更新履歴

更新:言語バージョンの指定

[C#]

ファイル ベース実行

[C#]

C# 14.0 の新機能

[C#]

更新:[雑記] オーバーロード解決

[C#]

型の分割定義 (partial)

[C#]

ブログ

C# 14 の破壊的変更点(First-class Span)

ファイナライザー

文字列リテラルを data セクションに UTF-8 で書き込む案

nameof(T<>)

First-class な Span 型


誤字等を見つけた場合や、ご意見・ご要望がございましたら、GitHub の Issues まで気兼ねなくご連絡ください。

[8]ページ先頭

©2009-2025 Movatter.jp