null 許容値型 (C# リファレンス)

"null 許容値型" T? は、基になる 値型 T のすべての値と、追加の null 値を表します。 たとえば、bool? 変数には、truefalsenull の 3 つの値のいずれかを割り当てることができます。 基になる値型 T を null 許容値型にすることはできません。

注意

C# 8.0 で、Null 許容参照型機能が導入されました。 詳細については、「null 許容参照型」を参照してください。 null 許容値型は、C# 2 から使用できます。

null 許容値型は、ジェネリック System.Nullable<T> 構造体のインスタンスです。 Nullable<T> または T? の代替可能な形式のいずれかで基になる型 T を持つ null 許容値型を参照できます。

null 許容値型は通常、基になる値型の未定義の値を表す必要があるときに使用します。 たとえば、ブール型 (bool) 変数で可能なのは、true または false のいずれかです。 ただし、一部のアプリケーションでは、変数の値が未定義または存在しない場合があります。 たとえば、データベース フィールドに、true または false が含まれている場合や、値がまったく含まれていない場合 (NULL) があります。 このシナリオでは、bool? 型を使用できます。

宣言と代入

値型は、対応する null 許容値型に暗黙的に変換できるため、基になる値型の場合と同様に、null 許容値型の変数に値を割り当てることができます。 null 値を代入することもできます。 次に例を示します。

double? pi = 3.14;
char? letter = 'a';

int m2 = 10;
int? m = m2;

bool? flag = null;

// An array of a nullable value type:
int?[] arr = new int?[10];

null 許容値型の既定値は null を表します。つまり、Nullable<T>.HasValue プロパティが false を返すインスタンスです。

null 許容値型のインスタンスの検査

C# 7.0 以降では、型パターンで is 演算子を使用して、null の null 許容値型のインスタンスを調べ、基になる型の値を取得することができます。

int? a = 42;
if (a is int valueOfA)
{
    Console.WriteLine($"a is {valueOfA}");
}
else
{
    Console.WriteLine("a does not have a value");
}
// Output:
// a is 42

null 許容値型の変数の値を確認して取得するには、常に次の読み取り専用プロパティを使用できます。

次の例では、HasValue プロパティを使用して、値を表示する前に変数に値が格納されているかどうかをテストします。

int? b = 10;
if (b.HasValue)
{
    Console.WriteLine($"b is {b.Value}");
}
else
{
    Console.WriteLine("b does not have a value");
}
// Output:
// b is 10

次の例に示すように、HasValue プロパティを使用する代わりに、null 許容値型の変数を null と比較することもできます。

int? c = 7;
if (c != null)
{
    Console.WriteLine($"c is {c.Value}");
}
else
{
    Console.WriteLine("c does not have a value");
}
// Output:
// c is 7

null 許容値型から基になる型への変換

null 許容値型の値を null 非許容値型の変数に割り当てる場合は、null の代わりに割り当てる値を指定する必要がある場合があります。 これを行うには、null 合体演算子 ?? を使用します (Nullable<T>.GetValueOrDefault(T) メソッドも同じ目的で使用することができます)。

int? a = 28;
int b = a ?? -1;
Console.WriteLine($"b is {b}");  // output: b is 28

int? c = null;
int d = c ?? -1;
Console.WriteLine($"d is {d}");  // output: d is -1

null の代わりに基になる値の型の既定値を使用する場合は、Nullable<T>.GetValueOrDefault() メソッドを使用します。

次の例に示すように、null 許容値型を null 非許容型に明示的にキャストすることもできます。

int? n = null;

//int m1 = n;    // Doesn't compile
int n2 = (int)n; // Compiles, but throws an exception if n is null

実行時に null 許容値型の値が null の場合は、明示的なキャストによって InvalidOperationException がスローされます。

null 非許容値型 T は、対応する null 許容値型 T? に暗黙的に変換されます。

リフト演算子

定義済みの単項演算子および 2 項演算子、または値型 T によってサポートされるオーバーロードされた任意の演算子は、対応する null 許容値型 T? でもサポートされます。 "リフト演算子" とも呼ばれるこれらの演算子では、一方または両方のオペランドが null の場合に null が生成されます。それ以外の場合は、そのオペランドに含まれている値を使用して結果が算出されます。 次に例を示します。

int? a = 10;
int? b = null;
int? c = 10;

a++;        // a is 11
a = a * c;  // a is 110
a = a + b;  // a is null

注意

bool? 型の場合、定義済みの & および | 演算子は、このセクションで説明されている規則に従わないことに注意してください。オペランドの 1 つが null の場合も、演算子の評価の結果は null 以外である可能性があります。 詳細については、「Boolean logical operators (ブール論理演算子)」記事の「Nullable Boolean logical operators (null 許容論理演算子)」セクションを参照してください。

比較演算子 <><=>= では、一方または両方のオペランドが null の場合、結果は false になります。それ以外の場合は、オペランドに含まれる値が比較されます。 ある比較 (たとえば、<=) から返される結果が false であっても、逆の比較 (>) から返される結果が true であるとは限りません。 次の例は、10 が

  • null 以上ではなく
  • null 未満でもないことを示します
int? a = 10;
Console.WriteLine($"{a} >= null is {a >= null}");
Console.WriteLine($"{a} < null is {a < null}");
Console.WriteLine($"{a} == null is {a == null}");
// Output:
// 10 >= null is False
// 10 < null is False
// 10 == null is False

int? b = null;
int? c = null;
Console.WriteLine($"null >= null is {b >= c}");
Console.WriteLine($"null == null is {b == c}");
// Output:
// null >= null is False
// null == null is True

等値演算子 == では、両方のオペランドが null の場合、結果は true になります。一方のオペランドだけが null の場合、結果は false です。それ以外の場合は、オペランドに含まれる値が比較されます。

非等値演算子 != では、両方のオペランドが null の場合、結果は false になります。一方のオペランドだけが null の場合、結果は true です。それ以外の場合は、オペランドに含まれる値が比較されます。

2 つの値型の間にユーザー定義の変換が存在する場合は、それに対応する null 許容値型間でも同じ変換を使用することができます。

ボックス化とボックス化解除

null 許容値型のインスタンス T? は、次のようにボックス化されます。

  • HasValuefalse を返した場合は、null 参照が生成されます。
  • HasValuetrueを返した場合は、Nullable<T> のインスタンスではなく、基になる値型 T の対応する値がボックス化されます。

次の例に示すように、値型 T のボックス化された値を、対応する null 許容値型 T? にボックス化解除できます。

int a = 41;
object aBoxed = a;
int? aNullable = (int?)aBoxed;
Console.WriteLine($"Value of aNullable: {aNullable}");

object aNullableBoxed = aNullable;
if (aNullableBoxed is int valueOfA)
{
    Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}");
}
// Output:
// Value of aNullable: 41
// aNullableBoxed is boxed int: 41

方法: null 許容値型を識別する

次の例は、System.Type インスタンスが構築された null 許容値型 (つまり、指定された型パラメーター T を使用する System.Nullable<T> 型) を表すかどうかを判断する方法を示しています。

Console.WriteLine($"int? is {(IsNullable(typeof(int?)) ? "nullable" : "non nullable")} value type");
Console.WriteLine($"int is {(IsNullable(typeof(int)) ? "nullable" : "non-nullable")} value type");

bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null;

// Output:
// int? is nullable value type
// int is non-nullable value type

例で示されているとおり、System.Type インスタンスの作成には、typeof 演算子を使用します。

インスタンスが null 許容値型かどうかを判断したい場合は、Type インスタンスが前述のコードでテストされるように、Object.GetType メソッドは使用しないでください。 null 許容値型のインスタンスで Object.GetType メソッドを呼び出した場合、そのインスタンスは Objectボクシングされます。 null 許容値型の null 以外のインスタンスのボックス化は、基になる型の値のボックス化と等しいので、GetType は、null 許容値型の基になる型を表す Type インスタンスを返します。

int? a = 17;
Type typeOfA = a.GetType();
Console.WriteLine(typeOfA.FullName);
// Output:
// System.Int32

また、インスタンスが null 許容値型であるかどうかを判断するために、is 演算子を使用しないでください。 次の例に示すように、is 演算子を使用して null 許容値型のインスタンスとその基になる型のインスタンスの型を区別することはできません。

int? a = 14;
if (a is int)
{
    Console.WriteLine("int? instance is compatible with int");
}

int b = 17;
if (b is int?)
{
    Console.WriteLine("int instance is compatible with int?");
}
// Output:
// int? instance is compatible with int
// int instance is compatible with int?

次の例のコードを使用すると、インスタンスが null 許容値型であるかどうかを判別することができます。

int? a = 14;
Console.WriteLine(IsOfNullableType(a));  // output: True

int b = 17;
Console.WriteLine(IsOfNullableType(b));  // output: False

bool IsOfNullableType<T>(T o)
{
    var type = typeof(T);
    return Nullable.GetUnderlyingType(type) != null;
}

注意

このセクションで説明されているメソッドは、null 許容参照型の場合には適用されません。

C# 言語仕様

詳細については、「C# 言語仕様」の次のセクションを参照してください。

関連項目