2014-10-23T23:46:15の更新内容

programming/netfx/nullable/index.wiki.txt

current previous
1,705 0,0
+
${smdncms:title,ヌル許容型}
+
${smdncms:header_title,ヌル許容型 (Nullable)}
+
${smdncms:keywords,ヌル許容型,Nullable型,Nullable<T>}
+
${smdncms:document_versions,codelang=cs,codelang=vb}
+

          
+
.NET Frameworkの参照型では値が未設定であることを表すためにnull/Nothingを使用することができます。 一方int/Integerなどの値型ではnullが代入できないため、未設定や無効な値であることを表すために0(あるいは-1や最大値などの値)を代入しておくという手法をとる場合があります。 しかし、0という具体的な値を使用してしまうと、未設定であるために0なのか、あるいは0という値が代入されているのか、二つの状態があいまいになるという問題があります。
+

          
+
こういった問題を解消するため、値型にもnullを代入できるようにしたものがヌル許容型(Nullable型)です。 ヌル許容型を使用すると、値型でもnullが代入されている状態、つまり具体的な値が設定されていない状態を表現することができます。
+

          
+
-関連するページ
+
--[[programming/netfx/valuetype_referencetype]]
+
--[[programming/netfx/basic_types/0_characteristics]]
+

          
+
*ヌル許容型
+
ヌル許容型を宣言する場合は、型名の後ろにクエスチョンマーク``?``を付けます。 例えばint型なら``int?``、Integer型なら``Integer?``のようになります。 型名の後ろに付けられる``?``はNull許容修飾子と呼ばれます。 ヌル許容型では、ヌル許容でない通常の型(ヌル非許容型)と同様に具体的な値を代入できるのに加え、``null``/``Nothing``を代入することができます。
+

          
+
#tabpage(codelang=cs,container-title=ヌル許容型変数の宣言)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    int  i1 = 3;    // 通常のint型
+
    int? i2 = 3;    // ヌル許容のint型
+
    int? i3 = null; // ヌル許容型ではnullを設定できる
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public 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
+
}}
+
#tabpage-end
+

          
+
整数型だけでなく、任意の値型をヌル許容型にすることができます。 独自に定義した構造体の場合も同様にNull許容修飾子``?``を付けるだけでヌル許容型として宣言することができます。
+

          
+

          
+
#tabpage(codelang=cs,container-title=ヌル許容型変数の宣言)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  // 独自に定義した構造体
+
  struct S {
+
  }
+

          
+
  public static void Main()
+
  {
+
    // ヌル許容のbool型
+
    bool? b = null;
+

          
+
    // ヌル許容のdouble型
+
    double? d = null;
+

          
+
    // ヌル許容の構造体型
+
    S? s = null;
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  ' 独自に定義した構造体
+
  Structure S
+
  End Structure
+

          
+
  Public Shared Sub Main()
+
    ' ヌル許容のBoolean型
+
    Dim b As Boolean? = Nothing
+

          
+
    ' ヌル許容のDouble型
+
    Dim d As Double? = Nothing
+

          
+
    ' ヌル許容の構造体型
+
    Dim s As S? = Nothing
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
ヌル許容型に値が設定されているかどうかを調べるには、``==``演算子, ``!=``演算子(VBでは``Is``演算子, ``IsNot``演算子)を使って``null``/``Nothing``と比較します。
+

          
+
#tabpage(codelang=cs,container-title=ヌル許容型変数に値が設定されているかテストする)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public 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);
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public 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
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
iには値が設定されていません
+
iには値 '3' が設定されています
+
}}
+

          
+
``null``/``Nothing``との比較の他に、&msdn(netfx,member,System.Nullable`1.HasValue){HasValueプロパティ};をチェックする方法もあります。 ヌル許容型に値が設定されている場合、HasValueプロパティはtrueになります。
+

          
+
#tabpage(codelang=cs,container-title=HasValueプロパティを使ったチェック)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    int? i = 3;
+

          
+
    // iに値が代入されているか調べる
+
    if (i.HasValue)
+
      Console.WriteLine("iには値 '{0}' が設定されています", i);
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public Shared Sub Main()
+
    Dim i As Integer? = 3
+

          
+
    ' iに値が代入されているか調べる
+
    If i.HasValue Then
+
      Console.WriteLine("iには値 '{0}' が設定されています", i)
+
    End If
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
iには値 '3' が設定されています
+
}}
+

          
+
参照型の場合とは異なり、ヌル許容型変数に``null``が設定されている場合にHasValueプロパティを参照してもヌル参照(NullReferenceException)にはなりません。
+

          
+
また、ヌル許容型から値を取り出してヌル非許容型に代入するには、明示的な型変換を行うか、&msdn(netfx,member,System.Nullable`1.Value){Valueプロパティ};を参照します。
+

          
+

          
+
#tabpage(codelang=cs,container-title=値の取り出し)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    int? i = 3;
+

          
+
    int j = (int)i; // 明示的な型変換
+
    int k = i.Value; // Valueプロパティを使って値を取り出す
+

          
+
    Console.WriteLine(j);
+
    Console.WriteLine(k);
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public Shared Sub Main()
+
    Dim i As Integer? = 3
+

          
+
    Dim j As Integer = CInt(i) ' 明示的な型変換
+
    Dim k As Integer = i.Value ' Valueプロパティを使って値を取り出す
+

          
+
    Console.WriteLine(j)
+
    Console.WriteLine(k)
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
3
+
3
+
}}
+

          
+
値が設定されていない状態でヌル非許容型へのキャストまたはValueプロパティを参照すると例外&msdn(netfx,type,System.InvalidOperationException){InvalidOperationException};がスローされます。 (&msdn(netfx,type,System.NullReferenceException){NullReferenceException};ではないので注意)
+

          
+
#tabpage(codelang=cs,container-title=設定されていない場合での値の取得)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    int? i = null;
+

          
+
    // 値が設定されていないのでInvalidOperationExceptionがスローされる
+
    int j = (int)i;
+
    int k = i.Value;
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public 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
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
Unhandled Exception:
+
System.InvalidOperationException: Nullable object must have a value.
+
  at System.Nullable`1[System.Int32].get_Value () [0x00000] in <filename unknown>:0 
+
  at Sample.Main () [0x00000] in <filename unknown>:0 
+
}}
+

          
+
この他、C#では値の取り出しにヌル合体演算子``??``を使用することができます。 これはヌル許容型の持つ値を参照する際、値が``null``だった場合に設定する値を指定する演算子です。 三項演算子``?:``と似ていますが、よりシンプルに記述できます。 VBではこれに相当する演算子は用意されていませんが、二項形式の``If``演算子を使用することで同様のことができます。 ([[programming/vb.net/operator_logical#IfOperator]])
+

          
+
#tabpage(codelang=cs,container-title=ヌル合体演算子)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public 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);
+
    Console.WriteLine(y);
+

          
+
    // 三項演算子を使って記述すると次のようになる
+
    int xx = a.HasValue ? a.Value : 16;
+
    int yy = b.HasValue ? b.Value : 16;
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public 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)
+
    Console.WriteLine(y)
+

          
+
    ' 三項演算子を使って記述すると次のようになる
+
    Dim xx As Integer = If(a.HasValue, a.Value, 16)
+
    Dim yy As Integer = If(b.HasValue, b.Value, 16)
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
16
+
3
+
}}
+

          
+
ヌル合体演算子に似たものとして&msdn(netfx,member,System.Nullable`1.GetValueOrDefault){GetValueOrDefaultメソッド};があります。 このメソッドは、ヌル許容型に値が設定されている場合はその値を、設定されていない場合は型のデフォルト値(``0``または``0``に相当する値)を返します。 値が設定されていない場合でもデフォルト値を使って処理を継続してもよい場合などに使用することができます。
+

          
+
#tabpage(codelang=cs,container-title=GetValueOrDefaultメソッド)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    int? a = null;
+
    int? b = 3;
+

          
+
    int x = a.GetValueOrDefault(); // aはnullなので、xにはデフォルト値0が代入される
+
    int y = b.GetValueOrDefault(); // bは3(値を持つ)なので、yには3が代入される
+

          
+
    Console.WriteLine(x);
+
    Console.WriteLine(y);
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public Shared Sub Main()
+
    Dim a As Integer? = Nothing
+
    Dim b As Integer? = 3
+

          
+
    Dim x As Integer = a.GetValueOrDefault() ' aはNothingなので、xにはデフォルト値0が代入される
+
    Dim y As Integer = b.GetValueOrDefault() ' bは3(値を持つ)なので、yには3が代入される
+

          
+
    Console.WriteLine(x)
+
    Console.WriteLine(y)
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
0
+
3
+
}}
+

          
+
型のデフォルト値については[[programming/netfx/basic_types/0_characteristics#DefaultValue]]を参照してください。
+

          
+
**ヌル許容型と演算
+
ヌル非許容型での演算と同様、ヌル許容型でも演算子を使って加算などの演算を行うことができます。 ただし、演算子の項のどちらか一方がnullの場合、演算結果もnullとなります。
+

          
+
#tabpage(codelang=cs,container-title=ヌル許容型と演算)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    int? x = 3;
+

          
+
    x = x + 1; // xには4が代入される
+

          
+
    Console.WriteLine(x);
+

          
+
    int? y = null;
+

          
+
    x = y + 1; // yがnullのため、xにはnullが代入される
+

          
+
    Console.WriteLine(x.HasValue);
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public Shared Sub Main()
+
    Dim x As Integer? = 3
+

          
+
    x = x + 1 ' xには4が代入される
+

          
+
    Console.WriteLine(x)
+

          
+
    Dim y As Integer? = Nothing
+

          
+
    x = y + 1 ' yがNothingのため、xにはNothingが代入される
+

          
+
    Console.WriteLine(x.HasValue)
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
4
+
False
+
}}
+

          
+

          
+
*Nullable<T>構造体
+
ヌル許容型の実体は&msdn(netfx,type,System.Nullable`1){Nullable<T>構造体};です。 そのため、次のようにNullable構造体を使ってヌル許容型の変数を宣言することもできます。 ヌル許容型の&msdn(netfx,member,System.Nullable`1.Value){Value};や&msdn(netfx,member,System.Nullable`1.HasValue){HasValue};などのプロパティはNullable構造体によって提供されます。
+

          
+
#tabpage(codelang=cs,container-title=Nullable<T>構造体とヌル許容型)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public 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);
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public 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
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
1
+
2
+
False
+
False
+
}}
+

          
+
言語が直接ヌル許容型をサポートしていない場合でも、ジェネリクスを使用できる言語であればNullable構造体を使用してヌル許容型を作成・使用することができます。
+

          
+
Nullable構造体の型引数``T``には値型のみを指定できます。 従って、参照型をベースにしたヌル許容型を作成することはできません。
+

          
+
#tabpage(codelang=cs,container-title=Nullable<T>構造体とヌル許容型)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public 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非許容の値型でなければなりません
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public 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
+
}}
+
#tabpage-end
+

          
+
**ヌル許容型と型情報
+
ヌル許容型で&msdn(netfx,member,System.Object.GetType){GetTypeメソッド};を使用して型情報を取得しようとすると、元になった型の型情報が返されます。 例えば``int?``では``System.Nullable<System.Int32>``ではなく``System.Int32``が返されます。
+

          
+
#tabpage(codelang=cs,container-title=ヌル許容型に対するGetType)
+
#code{{
+
using System;
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    // ヌル許容型
+
    int? x = 0;
+

          
+
    Console.WriteLine(x.GetType());
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Class Sample
+
  Public Shared Sub Main()
+
    ' ヌル許容型
+
    Dim x As Integer? = 0
+

          
+
    Console.WriteLine(x.GetType())
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
#prompt(実行結果){{
+
System.Int32
+
}}
+

          
+
またC#では、ヌル許容型を``is``演算子で比較する場合、元の型が同じであればヌル許容型でもヌル非許容型でも同一の型とみなされます。
+

          
+
#code(cs,ヌル許容型とis演算子での比較){{
+
using System;
+

          
+
class Sample {
+
  public 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");
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
x is int?
+
x is int
+
}}
+

          
+
C#・VBでは、型がヌル許容型かどうかを調べる手段は言語の機能としては用意されていません。
+

          
+
**ヌル許容型構造体でのフィールド・プロパティの値の変更
+
例えば次のようにヌル許容型の構造体でフィールド・プロパティの値を変更しようとした場合、コンパイルエラーとなります。
+

          
+
#tabpage(codelang=cs,container-title=ヌル許容型構造体でのフィールドの参照)
+
#code{{
+
using System;
+

          
+
struct S {
+
  public int F;
+
}
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    S? s = new S();
+

          
+
    if (s.HasValue) {
+
      // 構造体SのフィールドFに代入を行いたい
+
      s.Value.F = 3;
+
      // error CS1612: 変数ではないため、'S?.Value' の戻り値を変更できません。
+
    }
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Structure S
+
  Public F As Integer
+
End Structure
+

          
+
Class Sample
+
  Public 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
+
}}
+
#tabpage-end
+

          
+
このような場合、一旦ヌル非許容の一時変数を使って変更、再代入を行う必要があります。
+

          
+
#tabpage(codelang=cs,container-title=ヌル許容型構造体でのフィールドの変更)
+
#code{{
+
using System;
+

          
+
struct S {
+
  public int F;
+
}
+

          
+
class Sample {
+
  public static void Main()
+
  {
+
    S? s = new S();
+

          
+
    if (s.HasValue) {
+
      // いったん一時変数に代入する
+
      S temp = s.Value;
+

          
+
      // 代入した一時変数を使ってフィールドの値を変更する
+
      temp.F = 3;
+

          
+
      // もとのヌル許容型変数に代入しなおす
+
      s = temp;
+
    }
+
  }
+
}
+
}}
+
#tabpage(codelang=vb)
+
#code{{
+
Imports System
+

          
+
Structure S
+
  Public F As Integer
+
End Structure
+

          
+
Class Sample
+
  Public 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
+
}}
+
#tabpage-end
+

          
+
このようにする必要がある理由について詳しくは[[programming/netfx/valuetype_referencetype#value_type_property]]を参照してください。
+

          
+

          
+

          
+