.NET Frameworkの参照型では、値が未設定であることを表すためにnull
/Nothing
を使用することができます。 一方int
/Integer
などの値型では、null
を代入することはできません。 そのため、未設定や無効な状態であることを表すために0
(あるいは-1
や最大値などの値)を代入しておくという手法をとる場合があります。 しかし、0
という具体的な値を使用してしまうと、未設定であるために0
なのか、あるいは0
という有効な値が代入されているのか、これら二つの状態があいまいになるという問題があります。
こういった問題を解消するため、値型に対してもnull
を代入できるようにしたものがヌル許容型(Nullable型)です。 ヌル許容型を使用すると、値型でもnull
が代入されている状態を作り出すことができ、これにより具体的な値が設定されていない状態や無効な状態などを表現することができます。
ヌル許容型に関連して、以下のような演算子・修飾子が導入されています。 詳細については後述しますが、各記号の使い分けやヌル許容型の要点として以下の表をご覧ください。
名前 | 記号と使用例 | 機能・動作 |
---|---|---|
Null許容修飾子 |
int? n
List<int?> list
Dim n As Integer?
Dim list As List(Of Integer?)
|
値型のヌル許容型化
型をヌル許容にする、ヌル許容型を宣言する |
Null合体演算子 |
x = n ?? 3
|
ヌル許容型・参照型の非null化
値を参照して、nullの場合は非null値を設定する |
Null条件演算子 |
(list = nullとして)
len = list?.Length
arr = list?.ToArray()
len = list?.ToArray()?.Length
val = list?[0]
|
null参照のショートサーキット
インスタンスや戻り値がnullの場合に、後続するメンバ呼び出しの結果をnullにする |
ヌル許容型
ヌル許容型の宣言
ヌル許容型を宣言する場合は、型名の後ろにクエスチョンマーク?
を付けます。 例えばC#のint型ならint?
、VBのInteger型ならInteger?
のようになります。 ヌル許容型int?
では、ヌル許容でないint
(ヌル非許容型)に対して代入できる値に加えて、null
/Nothing
を代入することができるようになります。 ヌル許容型で型名の後ろに付けられる?
は、Null許容修飾子と呼ばれます。
using System;
class Sample {
static void Main()
{
int i1 = 3; // 通常のint型
int? i2 = 3; // ヌル許容のint型
int? i3 = null; // ヌル許容型ではnullを設定できる
}
}
Imports System
Class Sample
Shared Sub Main()
Dim i1 As Integer = 3 ' 通常のInteger型
Dim i2 As Integer? = 3 ' ヌル許容のInteger型
Dim i3 As Integer? = Nothing ' ヌル許容型ではNothingを設定できる
End Sub
End Class
整数型などの基本型だけでなく、任意の値型をヌル許容型にすることができます。 独自に定義した構造体の場合も同様に、Null許容修飾子?
を付けるだけでヌル許容型として宣言することができます。
using System;
class Sample {
// 独自に定義した構造体
struct S {
}
static void Main()
{
// ヌル許容のbool型
bool? b = null;
// ヌル許容のdouble型
double? d = null;
// ヌル許容の構造体型
S? s = null;
}
}
Imports System
Class Sample
' 独自に定義した構造体
Structure S
End Structure
Shared Sub Main()
' ヌル許容のBoolean型
Dim b As Boolean? = Nothing
' ヌル許容のDouble型
Dim d As Double? = Nothing
' ヌル許容の構造体型
Dim s As S? = Nothing
End Sub
End Class
構造体をヌル許容にする場合に関して、§.ヌル許容型構造体でのフィールド・プロパティの値の変更も参照してください。
ヌル許容型の配列・コレクション
ヌル許容型は配列やList<T>などのコレクションでも用いることができます。 int?[]
やList<int?>
といったように、配列・コレクションの型にNull許容修飾子?
をつければ、ヌル許容型の配列・コレクションを作成することができます。
配列やコレクションで複数の値を扱う際、要素の一部に空の状態(empty)や未設定の状態(undefined, uninitialized)を設定したい場合があります。 こういった場合、値型ではnull
を用いることができないため、例えば0
や-1
などに特別な意味を持たせる場合がありました。 しかし、0
や-1
に空や未設定などの意味を持たせても、それを認識していなければ単なる数値であることにかわりなく、意味が無視され他の数と同列に処理されてしまう可能性があります。 ヌル許容型を用いれば空や未設定の状態を表すためにnull
を用いることができるようになり、左記のような問題を避けることができます。
using System;
using System.Collections.Generic;
class Sample {
static void Main()
{
// ヌル許容のint型配列に要素としてnullを格納する
int?[] arr = new int?[] {0, 2, null, 1};
arr[0] = null; // 配列の要素にnullを設定する
// ヌル許容のint型Listに要素としてnullを格納する
List<int?> list = new List<int?>() {0, 2, null, 1};
list.Add(null); // Listの要素としてnullを追加する
}
}
Imports System
Imports System.Collections.Generic
Class Sample
Shared Sub Main()
' ヌル許容のInteger型配列に要素としてNothingを格納する
Dim arr?() As Integer = New Integer?() {0, 1, Nothing, 2}
arr(0) = Nothing ' 配列の要素にNothingを設定する
' ヌル許容のint型Listに要素としてNothingを格納する
Dim list As New List(Of Integer?)() From {0, 2, Nothing, 1}
list.Add(Nothing) ' Listの要素としてNothingを追加する
End Sub
End Class
この例ではListの初期化にコレクション初期化子を用いています。 コレクション初期化子を用いたインスタンスの作成についてはジェネリックコレクション(1) List §.コレクション初期化子を参照してください。
VBでヌル許容型の配列を宣言する場合、Null許容修飾子?
を付ける位置に注意する必要があります。 Null許容修飾子?
と、配列を表す配列修飾子()
は常にひと組で記述する必要があります。 変数の側に配列修飾子()
を付け、型名の後にNull許容修飾子?
を付けたり、またその逆に付けるとコンパイルエラーとなります。
Imports System
Class Sample
Shared Sub Main()
' このようにNull許容修飾子と配列修飾子はセットで付ける必要がある
Dim arr1 As Integer?()
Dim arr2?() As Integer
' このようにNull許容修飾子と配列修飾子を別々に付けることはできない
Dim arr3() As Integer?
' error BC33102: 変数とその型の両方で、Null 許容修飾子 '?' と配列修飾子 '(' および ')' を指定することはできません
Dim arr4? As Integer()
' error BC33102: 変数とその型の両方で、Null 許容修飾子 '?' と配列修飾子 '(' および ')' を指定することはできません
End Sub
End Class
値の設定状態のテスト
等号演算子・Is演算子によるテスト
ヌル許容型に有効な値(null
/Nothing
以外の値)が設定されているかどうかを調べるには、==
演算子, !=
演算子(VBではIs
演算子, IsNot
演算子)を使ってnull
/Nothing
と比較します。
using System;
class Sample {
static void Main()
{
int? i;
// ヌル許容型にnullを設定する
i = null;
// 等号演算子でヌル許容型に値が設定されてるかテストする
if (i == null)
Console.WriteLine("iには値が設定されていません");
else
Console.WriteLine("iには値 '{0}' が設定されています", i);
// ヌル許容型に値を設定する
i = 3;
// 等号演算子でヌル許容型に値が設定されてるかテストする
if (i == null)
Console.WriteLine("iには値が設定されていません");
else
Console.WriteLine("iには値 '{0}' が設定されています", i);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim i As Integer?
' ヌル許容型にNothingを設定する
i = Nothing
' Is演算子でヌル許容型に値が設定されてるかテストする
If i Is Nothing Then
Console.WriteLine("iには値が設定されていません")
Else
Console.WriteLine("iには値 '{0}' が設定されています", i)
End If
' ヌル許容型に値を設定する
i = 3
' Is演算子でヌル許容型に値が設定されてるかテストする
If i Is Nothing Then
Console.WriteLine("iには値が設定されていません")
Else
Console.WriteLine("iには値 '{0}' が設定されています", i)
End If
End Sub
End Class
iには値が設定されていません iには値 '3' が設定されています
HasValueプロパティによるテスト
null
/Nothing
との比較の他に、HasValueプロパティをチェックする方法もあります。 ヌル許容型に値が設定されている場合、HasValueプロパティはtrue
になります。
using System;
class Sample {
static void Main()
{
int? i = 3;
// iに値が代入されているか調べる
if (i.HasValue)
Console.WriteLine("iには値 '{0}' が設定されています", i);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim i As Integer? = 3
' iに値が代入されているか調べる
If i.HasValue Then
Console.WriteLine("iには値 '{0}' が設定されています", i)
End If
End Sub
End Class
iには値 '3' が設定されています
参照型の場合とは異なり、ヌル許容型変数にnull
が設定されている場合にHasValueプロパティを参照しても、ヌル参照(NullReferenceException)にはなりません。 ヌル許容型にnull
が設定されている(値が設定されていない)場合、HasValueプロパティはfalse
になります。
using System;
class Sample {
static void Main()
{
int? i = null; // nullを設定する
// nullが代入されていてもHasValueプロパティを参照できる(ヌル参照にはならない)
if (i.HasValue)
Console.WriteLine("iには値 '{0}' が設定されています", i);
else
Console.WriteLine("iには値が設定されていません");
}
}
Imports System
Class Sample
Shared Sub Main()
Dim i As Integer? = Nothing ' Nothingを設定する
' Nothingが代入されていてもHasValueプロパティを参照できる(ヌル参照にはならない)
If i.HasValue Then
Console.WriteLine("iには値 '{0}' が設定されています", i)
Else
Console.WriteLine("iには値が設定されていません")
End If
End Sub
End Class
iには値が設定されていません
値の参照・ヌル非許容型への変換
ヌル許容型から値を取り出してヌル非許容型に代入するには、ヌル非許容型への明示的な型変換を行うか、Valueプロパティを参照します。 明示的な型変換を行う場合、拡大変換となる型変換(int
→long
、byte
→int
など)であれば型の異なるヌル非許容型への代入を行うこともできます。
using System;
class Sample {
static void Main()
{
int? i = 3; // ヌル許容型
int j = (int)i; // 明示的な型変換によって値を取り出す
long k = (long)i; // 拡大変換となる型変換で値を取り出す
int l = i.Value; // Valueプロパティを使って値を取り出す
Console.WriteLine(j);
Console.WriteLine(k);
Console.WriteLine(l);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim i As Integer? = 3 ' ヌル許容型
Dim j As Integer = CInt(i) ' 明示的な型変換
Dim k As Long = CLng(i) ' 拡大変換となる型変換で値を取り出す
Dim l As Integer = i.Value ' Valueプロパティを使って値を取り出す
Console.WriteLine(j)
Console.WriteLine(k)
Console.WriteLine(l)
End Sub
End Class
3 3 3
Valueプロパティは取得専用のため、このプロパティを使って値を設定することはできません。 ヌル許容型への値を設定は、通常の変数と同様に直接代入して行います。
値が設定されていない状態で、ヌル非許容型へのキャストまたはValueプロパティを参照すると例外InvalidOperationExceptionがスローされます。 (NullReferenceExceptionではないので注意)
using System;
class Sample {
static void Main()
{
int? i = null;
// 値が設定されていないのでInvalidOperationExceptionがスローされる
int j = (int)i;
int k = i.Value;
}
}
Imports System
Class Sample
Shared Sub Main()
Dim i As Integer? = Nothing
' 値が設定されていないのでInvalidOperationExceptionがスローされる
Dim j As Integer = CInt(i)
Dim k As Integer = i.Value
End Sub
End Class
ハンドルされていない例外: System.InvalidOperationException: Null 許容のオブジェクトには値を指定しなければなりません。 場所 System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) 場所 Sample.Main()
Null合体演算子・If演算子による値の非null化
C#では値の取り出しにNull合体演算子??
を使用することもできます。 この演算子は、ヌル許容型の持つ値を参照する際、値がnull
だった場合に代替として用いる非null値を指定する演算子です。 三項演算子?:
と似ていますが、よりシンプルに記述できます。 例えばヌル許容型変数a
に対してx = a ?? 16
という式を記述した場合、a
が値を持っている場合はその値、持っていない場合は16
がx
に代入されます。
VBではこれに相当する演算子は用意されていませんが、二項形式のIf
演算子を使用することで同様のことができます。 (詳細:論理演算子 §.If演算子)
using System;
class Sample {
static void Main()
{
int? a = null;
int? b = 3;
int x = a ?? 16; // aはnullなので、xには16が代入される
int y = b ?? 16; // bは3(値を持っている)なので、yには3が代入される
Console.WriteLine("x = {0}", x);
Console.WriteLine("y = {0}", y);
// 三項演算子を使って記述すると次のようになる
int xx = a.HasValue ? a.Value : 16;
int yy = b.HasValue ? b.Value : 16;
}
}
Imports System
Class Sample
Shared Sub Main()
Dim a As Integer? = Nothing
Dim b As Integer? = 3
Dim x As Integer = If(a, 16) ' aはNothingなので、xには16が代入される
Dim y As Integer = If(b, 16) ' bは3(値を持っている)なので、yには3が代入される
Console.WriteLine("x = {0}", x)
Console.WriteLine("y = {0}", y)
' 三項形式のIf演算子を使って記述すると次のようになる
Dim xx As Integer = If(a.HasValue, a.Value, 16)
Dim yy As Integer = If(b.HasValue, b.Value, 16)
End Sub
End Class
x = 16 y = 3
Null合体演算子を用いることにより、値がnull
だった場合に別の値を代入するといった操作を、if文による条件分岐や三項演算子?:
を用いずに記述することができます。
これと同様の操作は後述のGetValueOrDefaultメソッドを使うことによっても行うことができます。
参照型でのNull合体演算子の使用
Null合体演算子は参照型に対しても用いることができます。 ヌル許容型に対して用いる場合と同様、第一項がnull
だった場合は第二項の値が用いられます。 VBの二項形式のIf演算子も参照型に対して同様に動作します。
using System;
class Sample {
static void Main()
{
string a = null;
string b = "foo";
string x = a ?? "(null)"; // aはnullなので、xには"(null)"が代入される
string y = b ?? "(null)"; // bは"foo"(値を持っている)なので、yには"foo"が代入される
Console.WriteLine("x = {0}", x);
Console.WriteLine("y = {0}", y);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim a As String = Nothing
Dim b As String = "foo"
Dim x As String = If(a, "(Nothing)") ' aはNothingなので、xには"(Nothing)"が代入される
Dim y As String = If(b, "(Nothing)") ' bは"foo"(値を持っている)なので、yには"foo"が代入される
Console.WriteLine("x = {0}", x)
Console.WriteLine("y = {0}", y)
End Sub
End Class
x = (null) y = foo
Null合体演算子と他の型への変換
Null合体演算子では、第一項・第二項ともに同一の型である必要があります(Option Strict Onの場合はVBのIf演算子も同様)。 例えば、次の例のようにヌル許容型の値を型変換するような場合、null
だった場合の値をにはNull合体演算子単体では処理できません。 この場合はif文や三項演算子を用いるか、あるいはNull条件演算子を用います。
using System;
using System.Linq;
class Sample {
static void Main()
{
// この配列をカンマ区切りで"0, 1, (empty), 2"と出力したい
int?[] arr = new int?[] {0, 1, null, 2};
// 前提: nullは空の文字列になるため、"0, 1, , 2"と表示されてしまう
// (nullの場合は"(empty)"と表示したい)
Console.WriteLine(string.Join(", ", arr));
// Null合体演算子では異なる型を指定できないのでコンパイルエラーとなる
//Console.WriteLine(string.Join(", ", arr.Select(val => val ?? "(empty)")));
// error CS0019: 演算子 '??' を 'int?' と 'string' 型のオペランドに適用することはできません。
// nullが設定されたヌル許容型に対してToString()を呼び出すと空の文字列(=nullではない)になるため、
// Null合体演算子の第二項が使用されない
Console.WriteLine(string.Join(", ", arr.Select(val => val.ToString() ?? "(empty)")));
// 'Null条件演算子'と組み合わせて用いると、目的の動作となる
Console.WriteLine(string.Join(", ", arr.Select(val => val?.ToString() ?? "(empty)")));
}
}
Option Strict On
Imports System
Imports System.Linq
Class Sample
Shared Sub Main()
' この配列をカンマ区切りで"0, 1, (empty), 2"と出力したい
Dim arr?() As Integer = New Integer?() {0, 1, Nothing, 2}
' 前提: Nothingは空の文字列になるため、"0, 1, , 2"と表示されてしまう
' (Nothingの場合は"(empty)"と表示したい)
Console.WriteLine(String.Join(", ", arr))
' Option Strict Onの場合、If演算子では異なる型を指定できないのでコンパイルエラーとなる
'Console.WriteLine(String.Join(", ", arr.Select(Function(val) If(val, "(empty)"))))
' error BC33110: バイナリ 'If' 演算子の 1 番目と 2 番目のオペランドの共通型を推論できません
' Nothingが設定されたヌル許容型に対してToString()を呼び出すと空の文字列(=Nothingではない)になるため、
' If演算子の第二項が使用されない
Console.WriteLine(String.Join(", ", arr.Select(Function(val) If(val.ToString(), "(empty)"))))
' "Nothing条件演算子"と組み合わせて用いると、目的の動作となる
Console.WriteLine(String.Join(", ", arr.Select(Function(val) If(val?.ToString(), "(empty)"))))
End Sub
End Class
0, 1, , 2 0, 1, , 2 0, 1, (empty), 2
Null条件演算子については§.Null条件演算子で別途解説します。
GetValueOrDefaultメソッドによる値の非null化
Null合体演算子に似たものとして、GetValueOrDefaultメソッドがあります。 このメソッドは、ヌル許容型に値が設定されている場合はその値を、設定されていない場合は型のデフォルト値(0
または0
に相当する値)を返します。 0
以外の値をデフォルト値としたい場合は、引数でデフォルト値として使用する値を指定することもできます。 戻り値はヌル非許容型となるため、デフォルト値としてnull
/Nothing
を指定することはできません。
このメソッドは、ヌル許容型に値が設定されていない場合はデフォルト値を使って処理を継続させたい場合などに使用することができます。
using System;
class Sample {
static void Main()
{
int? a = null;
int? b = null;
int? c = 3;
int x = a.GetValueOrDefault(); // aはnullなので、xにはデフォルト値0が代入される
int y = b.GetValueOrDefault(-1); // bはnullなので、yにはデフォルト値として-1が代入される
int z = c.GetValueOrDefault(-1); // cは3(値を持っている)なので、zにはcの値=3が代入される
Console.WriteLine("x = {0}", x);
Console.WriteLine("y = {0}", y);
Console.WriteLine("z = {0}", z);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim a As Integer? = Nothing
Dim b As Integer? = Nothing
Dim c As Integer? = 3
Dim x As Integer = a.GetValueOrDefault() ' aはNothingなので、xにはデフォルト値0が代入される
Dim y As Integer = b.GetValueOrDefault(-1) ' bはNothingなので、yにはデフォルト値として-1が代入される
Dim z As Integer = c.GetValueOrDefault(-1) ' cは3(値を持っている)なので、zにはcの値=3が代入される
Console.WriteLine("x = {0}", x)
Console.WriteLine("y = {0}", y)
Console.WriteLine("z = {0}", z)
End Sub
End Class
x = 0 y = -1 z = 3
型のデフォルト値については型の種類・サイズ・精度・値域 §.型のデフォルト値を参照してください。
デフォルト値をnull
として処理を継続させたいような場合は、Null条件演算子を使うことができます。
ヌル許容型での演算
ヌル許容型に対しても、ヌル非許容型での演算と同様に加算などの演算を行うことができます。 ただし、演算子の項のどちらか一方がnull
の場合は、演算結果もnull
となります。 これに従い、演算結果も必然的にヌル許容型となります。 そのため、ヌル許容型を項に含む演算結果をヌル非許容型に代入することはできません。
using System;
class Sample {
static void Main()
{
int? a = 3;
int? x = a + 1; // xには4が代入される
Console.WriteLine(x);
// ヌル許容型の演算結果をヌル非許容型へ代入することはできない
// (項のどちらかがnullであれば演算結果がnullとなるため)
//int y = a + 1;
// error CS0266: 型 'int?' を 'int' に暗黙的に変換できません。明示的な変換が存在します。(cast が不足していないかどうかを確認してください)
}
}
Option Strict On
Imports System
Class Sample
Shared Sub Main()
Dim a As Integer? = 3
Dim x As Integer? = a + 1 ' xには4が代入される
Console.WriteLine(x)
' ヌル許容型の演算結果をヌル非許容型へ代入することはできない
' (項のどちらかがNothingであれば演算結果がNothingとなるため)
'Dim y As Integer = a + 1
' error BC30512: Option Strict On では、'Integer?' から 'Integer' への暗黙的な変換はできません
End Sub
End Class
4
using System;
class Sample {
static void Main()
{
int? a = null;
int? x = a + 1; // aはnullのため、xにはnullが代入される
Console.WriteLine(x.HasValue);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim a As Integer? = Nothing
Dim x As Integer? = a + 1 ' aはNothingのため、xにはNothingが代入される
Console.WriteLine(x.HasValue)
End Sub
End Class
False
ヌル許容型でのToStringメソッドによる文字列化
ヌル許容型ではToStringメソッドを呼び出すことはできますが、引数で書式を指定することはできません。 これは、ヌル許容型を構成するNullable<T>構造体のToStringメソッドには書式を指定するバージョンがないためです。 書式と同様、カルチャなどの書式プロバイダを指定することもできません。
そのため、書式を指定してヌル許容型の値を文字列化したい場合は、String.Formatメソッドを使うか、Valueプロパティを参照してその値に対してToStringメソッドを呼び出す必要があります。
using System;
class Sample {
static void Main()
{
int i = 3;
int? ni = 3;
// 有効桁数4の自然数(N)として文字列化したい
Console.WriteLine(i.ToString("N4"));
Console.WriteLine(ni.ToString("N4")); // ヌル許容型では書式を指定したToStringができない
// error CS1501: 引数を '1' 個指定できる、メソッド 'ToString' のオーバーロードはありません。
// この場合、String.Formatメソッドによって文字列化する必要がある
Console.WriteLine(string.Format("{0:N4}", ni));
// あるいは、Valueプロパティの値に対してToStringメソッドを呼び出す
Console.WriteLine(ni.Value.ToString("N4"));
}
}
Imports System
Class Sample
Shared Sub Main()
Dim i As Integer = 3
Dim ni As Integer? = 3
' 有効桁数4の自然数(N)として文字列化したい
Console.WriteLine(i.ToString("N4"))
Console.WriteLine(ni.ToString("N4")) ' ヌル許容型では書式を指定したToStringができない
' error BC30512: Option Strict On では、'String' から 'Integer' への暗黙的な変換はできません
' この場合、String.Formatメソッドによって文字列化する必要がある
Console.WriteLine(String.Format("{0:N4}", ni))
' あるいは、Valueプロパティの値に対してToStringメソッドを呼び出す
Console.WriteLine(ni.Value.ToString("N4"))
End Sub
End Class
Nullable<T>構造体については後述の§.Nullable<T>構造体を参照してください。
書式を指定した文字列化に関しては書式指定子、書式プロバイダについてはカルチャと書式・テキスト処理・暦または書式の定義と実装を参照してください。
ヌル許容型構造体でのフィールド・プロパティの値の変更
次の例のように、ヌル許容型の構造体でフィールド・プロパティの値を変更しようとした場合、コンパイルエラーとなります。
using System;
struct S {
public int F;
}
class Sample {
static void Main()
{
S? s = new S();
if (s.HasValue) {
// 構造体SのフィールドFに代入を行いたい
s.Value.F = 3;
// error CS1612: 変数ではないため、'S?.Value' の戻り値を変更できません。
}
}
}
Imports System
Structure S
Public F As Integer
End Structure
Class Sample
Shared Sub Main()
Dim s As S? = New S()
If s.HasValue Then
' 構造体SのフィールドFに代入を行いたい
s.Value.F = 3
' error BC30068: Expression は値であるため、代入式のターゲットにすることはできません。
End If
End Sub
End Class
このような場合、一旦ヌル非許容の一時変数を使って変更、再代入を行う必要があります。
using System;
struct S {
public int F;
}
class Sample {
static void Main()
{
S? s = new S();
if (s.HasValue) {
// いったん一時変数に代入する
S temp = s.Value;
// 代入した一時変数を使ってフィールドの値を変更する
temp.F = 3;
// もとのヌル許容型変数に代入しなおす
s = temp;
}
}
}
Imports System
Structure S
Public F As Integer
End Structure
Class Sample
Shared Sub Main()
Dim s As S? = New S()
If s.HasValue Then
' 構造体SのフィールドFに代入を行いたい
s.Value.F = 3
' いったん一時変数に代入する
Dim temp As S = s.Value
' 代入した一時変数を使ってフィールドの値を変更する
temp.F = 3
' もとのヌル許容型変数に代入しなおす
s = temp
End If
End Sub
End Class
このようにする必要がある理由については値型と参照型 §.値型のプロパティ・インデクサで詳しく解説しています。
Null条件演算子
ヌル許容型や参照型のメソッドを呼び出す場合など、ヌル参照を避ける目的で次のように事前にif文などによるチェックを行う場面が多くあります。 C# 6.0およびVisual Basic 2015以降ではNull条件演算子が新たに導入されていて、このようなヌルチェックの処理をシンプルに記述することができます。
using System;
class Sample {
static void Main()
{
int[] arr = null;
// 配列の長さを取得して変数lenに代入する
// (配列がnullの場合はnullが代入される)
int? len = arr?.Length;
}
}
Imports System
Class Sample
Shared Sub Main()
Dim arr() As Integer = Nothing
' 配列の長さを取得して変数lenに代入する
' (配列がNothingの場合はNothingが代入される)
Dim len As Integer? = arr?.Length
End Sub
End Class
using System;
class Sample {
static void Main()
{
int[] arr = null;
// 配列の長さを取得して変数lenに代入する
// (配列がnullの場合は0を代入する)
int len;
if (arr == null)
len = 0;
else
len = arr.Length;
}
}
Imports System
Class Sample
Shared Sub Main()
Dim arr() As Integer = Nothing
' 配列の長さを取得して変数lenに代入する
' (配列がnullの場合は0を代入する)
Dim len As Integer
If arr Is Nothing Then
len = 0
Else
len = arr.Length
End If
End Sub
End Class
このようにNull条件演算子は、プロパティ参照やメソッド呼び出しなど、メンバへのアクセスを行おうとした場合にnull
かどうかのチェックを行います。 この時、null
だった場合は呼び出しを行わず、null
を結果として返します。 Null条件演算子を記述した場合でも、呼び出し元(Null条件演算子の左項)がnull
でなければ通常のメンバアクセスと同様に扱われます。
またNull条件演算子を使う場合、その結果はメンバへのアクセスによって得られる値(プロパティの値・メソッドの戻り値)か、あるいはnull
のどちらかになります。 そのため、必然的にNull条件演算子の左辺は参照型あるいはヌル許容型となります。
using System;
class Sample {
static void Main()
{
int[] arr = new int[] {0, 1, 2, 3, 4};
// 変数にnull以外が代入されている場合
int? len1 = arr?.Length; // arrはnullではないため、len1にはarr.Lengthの値が代入される
Console.WriteLine("len1 = {0}", len1);
// 変数にnullが代入されている場合
arr = null;
int? len2 = arr?.Length; // arrはnullのためLengthプロパティは参照されず、len2にはnullが代入される
Console.WriteLine("len2 = {0}", len2);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim arr() As Integer = New Integer() {0, 1, 2, 3, 4}
' 変数にNothing以外が代入されている場合
Dim len1 As Integer? = arr?.Length ' arrはnullではないため、len1にはarr.Lengthの値が代入される
Console.WriteLine("len1 = {0}", len1)
' 変数にNothingが代入されている場合
arr = Nothing
Dim len2 As Integer? = arr?.Length ' arrはnullのためLengthプロパティは参照されず、len2にはnullが代入される
Console.WriteLine("len2 = {0}", len2)
End Sub
End Class
len1 = 5 len2 =
Null条件演算子は、メソッド・プロパティ・インデクサなどの参照に前置することができます。 また、戻り値を持たないメソッドの呼び出しにも使うことができます。 ただし、インデクサの設定に用いることはできません。
using System;
using System.Collections.Generic;
class Sample {
static void Main()
{
List<int> list = null;
int? len = list?.Count; // プロパティ参照
int? index = list?.IndexOf(2); // メソッド呼び出し
list?.Clear(); // 戻り値のないメソッドの呼び出し
int? e = list?[0]; // インデクサ参照
// インデクサの設定ではNull条件演算子を用いることはできない
//list?[0] = 10;
// error CS0131: 代入式の左辺には変数、プロパティ、またはインデクサーを指定してください。
}
}
Imports System
Imports System.Collections.Generic
Class Sample
Shared Sub Main()
Dim list As List(Of Integer) = Nothing
Dim len As Integer? = list?.Count ' プロパティ参照
Dim index As Integer? = list?.IndexOf(2) ' メソッド呼び出し
list?.Clear() ' 戻り値のないメソッドの呼び出し
Dim e As Integer? = list?(0) ' インデクサ参照
' インデクサの設定ではNull条件演算子を用いることはできない
'list?(0) = 10
' error BC30068: Expression は値であるため、代入式のターゲットにすることはできません。
End Sub
End Class
さらに、メソッドチェイン(戻り値から連続するメソッド呼び出し)においてもNull条件演算子を用いることができます。 この場合、呼び出し元や戻り値がnull
となった時点で結果はnull
に確定し、それ以降の呼び出しが行われなくなります(ショートサーキット)。
using System;
class Sample {
static void Main()
{
string str = null;
// メソッドチェインでNull条件演算子を使う
int? len = str?.Trim()?.Substring(5)?.Length;
}
}
Imports System
Imports System.Collections.Generic
Class Sample
Shared Sub Main()
Dim str As String = Nothing
' メソッドチェインでNull条件演算子を使う
Dim len As Integer? = str?.Trim()?.Substring(5)?.Length
End Sub
End Class
演算子によってnull
を後続に伝播させることができるとも捉えることができるため、Null条件演算子の導入前はNull伝播演算子(null propagating operator)とも呼ばれていました。
このほか、イベントの発生にもNull条件演算子を用いることができます。 イベントの発生は、Null条件演算子によって記述がシンプルになる好例です。
using System;
using System.ComponentModel;
class C {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged1(string propertyName)
{
// イベントPropertyChangedを発生させる (if文によるヌルチェック)
var ev = this.PropertyChanged;
if (ev != null)
ev(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void RaisePropertyChanged2(string propertyName)
{
// イベントPropertyChangedを発生させる (Null条件演算子によるnullのショートサーキット)
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
イベントの発生についてはイベント、INotifyPropertyChangedとPropertyChangedEventHandlerによるプロパティ変更の通知についてはプロパティ §.プロパティ変更の通知 (INotifyPropertyChanged)で解説しています。
Nullable<T>構造体
ヌル許容型の実体はNullable<T>構造体です。 そのため、次のようにNullable構造体を使ってヌル許容型の変数を宣言することもできます。 ヌル許容型のValueやHasValueなどのプロパティはNullable構造体によって提供されます。
using System;
class Sample {
static void Main()
{
// 以下の二つはどちらもNullable<int>を宣言している
int? a = 1;
Nullable<int> b = 2;
Console.WriteLine(a.Value);
Console.WriteLine(b.Value);
a = null;
b = null;
Console.WriteLine(a.HasValue);
Console.WriteLine(b.HasValue);
}
}
Imports System
Class Sample
Shared Sub Main()
' 以下の二つはどちらもNullable(Of Integer)を宣言している
Dim a As Integer? = 1
Dim b As Nullable(Of Integer) = 2
Console.WriteLine(a.Value)
Console.WriteLine(b.Value)
a = Nothing
b = Nothing
Console.WriteLine(a.HasValue)
Console.WriteLine(b.HasValue)
End Sub
End Class
1 2 False False
言語が直接ヌル許容型をサポートしていない場合でも、ジェネリクスを使用できる言語であればNullable構造体を使用してヌル許容型を作成・使用することができます。
Nullable構造体の型引数T
には値型のみを指定できます。 従って、参照型をベースにしたヌル許容型を作成することはできません。
using System;
class Sample {
static void Main()
{
// 値型をベースにしたヌル許容型
int? i = null;
bool? b = null;
Nullable<double> d = null;
// 参照型をベースにしたヌル許容型を作成することはできない
string? s = null;
// error CS0453: 型 'string' は、ジェネリック型のパラメーター 'T'、またはメソッド 'System.Nullable<T>' として使用するために、Null非許容の値型でなければなりません
Nullable<object> o = null;
// error CS0453: 型 'object' は、ジェネリック型のパラメーター 'T'、またはメソッド 'System.Nullable<T>' として使用するために、Null非許容の値型でなければなりません
}
}
Imports System
Class Sample
Shared Sub Main()
' 値型をベースにしたヌル許容型
Dim i As Integer? = Nothing
Dim b As Boolean = Nothing
Dim d As Double = Nothing
' 参照型をベースにしたヌル許容型を作成することはできない
Dim s As String? = Nothing
' error BC33101: 'Nullable' または Null 許容修飾子 '?' と共に使用するためには、型 'String' は 'Structure' に制約されている値型または型引数である必要があります
Dim o As Nullable(Of Object) = Nothing
' error BC33101: 'Nullable' または Null 許容修飾子 '?' と共に使用するためには、型 'Object' は 'Structure' に制約されている値型または型引数である必要があります
End Sub
End Class
また、Nullable構造体を使ってヌル許容型のヌル許容型を構成するといった入れ子にすることもできません。
ヌル許容型と型情報
ヌル許容型でGetTypeメソッドを使用して型情報を取得しようとすると、ヌル許容型の元になった型の型情報が返されます。 例えばint?
ではSystem.Nullable<System.Int32>
ではなくSystem.Int32
が返されます。
using System;
class Sample {
static void Main()
{
// ヌル許容型
int? x = 0;
Console.WriteLine(x.GetType()); // ベースとなる型(int)の型情報が返される
}
}
Imports System
Class Sample
Shared Sub Main()
' ヌル許容型
Dim x As Integer? = 0
Console.WriteLine(x.GetType()) ' ベースとなる型(Integer)の型情報が返される
End Sub
End Class
System.Int32
逆に、typeof
演算子・GetType
演算子によって型情報を取得する場合、System.Int32
ではなくSystem.Nullable<System.Int32>
が返されます。
using System;
class Sample {
static void Main()
{
// typeof演算子によってint?の型情報を取得する
Console.WriteLine(typeof(int?));
}
}
Imports System
Class Sample
Shared Sub Main()
' GetType演算子によってint?の型情報を取得する
Console.WriteLine(GetType(Integer?))
End Sub
End Class
System.Nullable`1[System.Int32]
またC#では、ヌル許容型をis
演算子で比較する場合、元の型が同じであればヌル許容型でもヌル非許容型でも同一の型とみなされます。
using System;
class Sample {
static void Main()
{
// ヌル許容型
int? x = 0;
// 以下のどちらもtrueとなる
if (x is int?)
Console.WriteLine("x is int?");
if (x is int)
Console.WriteLine("x is int");
}
}
x is int? x is int
このように、is
演算子では方がヌル許容型かヌル非許容型かを区別することができません。 ヌル許容型かどうかを調べる方法については後述の§.型あるいは値がヌル許容型かどうかを調べるで解説します。
型あるいは値がヌル許容型かどうかを調べる
C#・VBでは、型がヌル許容型かどうかを調べる手段は言語の機能としては用意されていません。 代わりに、Nullable.GetUnderlyingTypeメソッドを使って調べることができます。
Nullable.GetUnderlyingTypeメソッドは、ヌル許容型のベースとなる型の型情報(System.Type)を返します(例えばint?
ならint
)。 ヌル許容型でない場合、このメソッドはnull
を返すので、これによって型がヌル許容型かそうでないかを判別することができます。
using System;
class Sample {
static void Main()
{
Console.WriteLine("int? : {0}", Nullable.GetUnderlyingType(typeof(int?)));
Console.WriteLine("int : {0}", Nullable.GetUnderlyingType(typeof(int)));
Console.WriteLine("object : {0}", Nullable.GetUnderlyingType(typeof(object)));
Console.WriteLine("string : {0}", Nullable.GetUnderlyingType(typeof(string)));
}
}
Imports System
Class Sample
Shared Sub Main()
Console.WriteLine("Integer? : {0}", Nullable.GetUnderlyingType(GetType(Integer?)))
Console.WriteLine("Integer : {0}", Nullable.GetUnderlyingType(GetType(Integer)))
Console.WriteLine("Object : {0}", Nullable.GetUnderlyingType(GetType(Object)))
Console.WriteLine("String : {0}", Nullable.GetUnderlyingType(GetType(String)))
End Sub
End Class
int? : System.Int32 int : object : string :
§.ヌル許容型と型情報でも述べているとおり、ヌル許容型でのGetType()メソッドによる型情報の取得やis演算子
による比較では、ベースとなっている型情報に対して行われるため、これを用いてヌル許容型かどうかを調べることができません。 値(インスタンス)がヌル許容型かどうかを調べるには、いくつか方法が考えられます。
ひとつは、以下のようなジェネリックメソッドを用意し、型引数Tに対してNullable.GetUnderlyingTypeを呼び出すことでヌル許容型かどうかを判別する方法です。 ただし、この方法ではObject
型に代入されたヌル許容型の値は、ヌル非許容型として判別されます。
using System;
class Sample {
// 引数で与えられた値がヌル許容型かどうかを調べる
static bool IsNullable<T>(T val)
{
// 型引数Tからベースとなる型を取得できるかどうかでヌル許容型かどうかを判別する
return Nullable.GetUnderlyingType(typeof(T)) != null;
}
static void Main()
{
int? x = 0;
int y = 0;
Console.WriteLine("x: {0}", IsNullable(x));
Console.WriteLine("y: {0}", IsNullable(y));
// この方法ではobject型に代入したヌル許容型を正しく判別できない
object o = x;
Console.WriteLine("o: {0}", IsNullable(o));
}
}
Imports System
Class Sample
' 引数に指定された値がヌル許容型かどうかを調べる
Function Shared IsNullable(Of T)(ByVal val As T) As Boolean
' 型引数Tからベースとなる型を取得できるかどうかでヌル許容型かどうかを判別する
Return (Nullable.GetUnderlyingType(GetType(T)) IsNot Nothing)
End Function
Shared Sub Main()
Dim x As Integer? = 0
Dim y As Integer = 0
Console.WriteLine("x: {0}", IsNullable(x))
Console.WriteLine("y: {0}", IsNullable(y))
' この方法ではobject型に代入したヌル許容型を正しく判別できない
Dim o As Object = x
Console.WriteLine("o: {0}", IsNullable(o))
End Sub
End Class
x: True y: False o: False
この他にもc# - How to check if an object is nullable? - Stack Overflowにて様々な手法が掲載されています。
Nullable<T>のデフォルト値
default(T)
やnew
によって作成されるデフォルト状態のNullable<T>(Nullable<T>の規定値)は、HasValueがfalse
であり、null
が設定されている状態と等しくなります。
using System;
class Sample {
static void Main()
{
var x = new Nullable<int>();
var y = default(Nullable<int>);
Console.WriteLine(x.HasValue);
Console.WriteLine(y.HasValue);
}
}
Imports System
Class Sample
Shared Sub Main()
Dim x As New Nullable(Of Integer)()
Dim y As Nullable(Of Integer) = Nothing
' Dim z As Integer = Nothing (Integerのデフォルト値の取得)
Console.WriteLine(x.HasValue)
Console.WriteLine(y.HasValue)
End Sub
End Class
False False
ヌル非許容の参照型
値型ValType
におけるヌル許容型ValType?
に対して、参照型RefType
をヌル非許容にした型RefType!
のようなものは現時点では用意されておらず、作成することはできません。 ヌル非許容型を使用したい場合は、struct
によって値型とするか、契約プログラミングの手法を用いるなど、他の手段によってnull
を非許容とする制約を施すほかありません。