初期化が必要な値型の変数を扱うとき、例えば数値型の変数は初期値が 0 ですが、この 0 が初期化していない(無効な状態)としての 0 なのか、適切な初期化を行った結果 0 になっているのかを区別することができませんでした。 これを区別するためには、この変数とは別に初期化済みかどうかを判断するための bool 変数が必要になりました。
C# 2.0 では、この問題をよりスマートに解決する方法として Nullable Type を追加しています。 Nullable Type とは、.NET Framework に新たに追加された System.Nullable<T> 構造体型のことを表していますが、同時に C# の言語仕様にも int や float のような基本的な型として組み込まれています。 NUllable Type は Generics を使った例としても興味深いシステムです。
public struct Nullable<T> : IFormattable, IComparable, IComparable<Nullable<T>>, INullableValue where T : struct
この構造体は値型に制約される型パラメータ T が宣言されています。 この T に対して null を表現可能にしたい値型を指定します。 このクラスのコンストラクタは次のように定義されています。
public Nullable(T value);
value には、この構造体が表す値を指定します。
Nullable Type の基本は INullableValue インタフェースで宣言されているメソッドに注目します。 このインタフェースでは、Nullable Type が保有する値を返す Value プロパティと、値が存在するかどうかを表す HasValue プロパティが宣言されています。 HasValue プロパティが FALSE を返した場合、値は null であると解釈することができます。
public T Value {get;}
public virtual bool HasValue {get;}
Value プロパティは読み取り専用プロパティとして宣言されています。 では Nullable<T> 構造体が null を表すように指定するにはどのようにすればよいのでしょうか。 Nullable() コンストラクタに指定する引数は T 型パラメータが値型の制約を受けているため null を指定することはできません。 null を表す Nullable<T> オブジェクトを生成するには、単純に Nullable<T> 構造体型の変数に対して null を代入します。 struct 型変数に null を代入しているかのような表現ですが、自動的に null を表す Nullable<T> オブジェクトに変換されます。
因みに Nullable<T> 構造体変数の初期値は null を表しています。
using System; class Test { static void Main() { Nullable<int> iValue = new Nullable<int>(10); Nullable<bool> bValue = new Nullable<bool>(true); ShowNullable(iValue); ShowNullable(bValue); iValue = null; ShowNullable(iValue); } static void ShowNullable<T>(Nullable<T> nv) where T : struct { Console.Write("Type=" + nv.GetType() + ", "); if (nv.HasValue) Console.WriteLine("Value=" + nv.Value); else Console.WriteLine("Value=null"); } }
Type=System.Nullable`1[System.Int32], Value=10 Type=System.Nullable`1[System.Boolean], Value=True Type=System.Nullable`1[System.Int32], Value=null
このプログラムでは int 型の Nullable と bool 型の Nullable を作成しています。 Nullable 自体は型パラメータを持つ構造体型で、内部に値を表す Value プロパティを持つため、ここから値を得ることができます。 ただし、Nullable を参照する場合は null の可能性があるため HasValue プロパティを調べてください。 null を表す Nullable に対して Value を参照した場合は例外が発生してしまいます。
Nullable Type を使うたびに System.Nullable クラスを指定するのはやや面倒です。 C# 言語では int キーワードが System.Int32 と同義であるように、Nullable に対するショートカットも新しく追加しています。 それが ? 型修飾子です。
? 型修飾子は、値型にのみ有効な修飾子で型名の直後に ? キーワードを指定します。 例えば int 型の変数宣言に ? 型修飾子を指定する場合は次のように記述します。
int? x;
この場合、x 変数は int 型ではなく System.Nullable<int> 型であると認識されます。 すなわち、? 型修飾子を指定された値型は System.Nullable<型> のエイリアスと考えることができ、同義なのです。
class Test { static void Main() { int? x = new int?(10); bool? b = new bool?(true); System.Console.WriteLine(x.GetType()); System.Console.WriteLine(b.GetType()); } }
int? や bool? は System.Nullable<int> や System.Nullable<bool> と同じです。 そのため、変数宣言以外に new 演算子によるインスタンス生成式でも利用しています。 この修飾子によって、値型の名前のまま直観的に Nullable Type を利用することができます。
Nullable<T> 型のオブジェクトに対して null を直接代入することができたように、値型を直接 Nullable<T> オブジェクトに代入することができます。 また、同様に Nullable<T> オブジェクトを直接、通常の値型のように振舞わせることもできます。 これらの型変換は全て暗黙的に行われます。
例えば int? x = 10; は int? x = new int?(10) と同じであると考えることができます。 同様に int i = x; は int i = x.Value と同じです。 ただし、Nullable<T> 型を通常の値型に変換する場合、null の可能性があるので注意してください。
class Test { static void Main() { int? x = 10; bool? b = true; System.Console.WriteLine("x.Value=" + x); System.Console.WriteLine("b.Value=" + b); x = x + 100; System.Console.WriteLine("x.Value=" + x); } }
このプログラムでは Nullable<int> 変数に対して整数リテラルを代入していますし、Nullable<bool> に対しても true を代入しています。 これらは、暗黙的に Nullable<T> 構造体型に変換されて変数に代入されます。
また、x = x + 100 や WriteLine() メソッドで Nullable<T> 構造体変数を指定するだけで内包している値を得られていることがわかります。 これは、純粋に x.Value や b.Value の省略であると考えることができます。 そのため、Nullable<T> 型の変数と通常の値型との間で比較演算などを行うことも、なんら問題にはなりません。
class Test { static void Main() { int? x = 10; int y = 100; System.Console.WriteLine("x == 10 : " + (x == 10)); System.Console.WriteLine("x < y : " + (x < y)); System.Console.WriteLine("x > y : " + (x > y)); } }
x == 10 : True x < y : True x > y : False
このプログラムでは、Nullable<int> 型の変数 x と、整数リテラルや int 型の変数 y を比較演算しています。 この場合、x は x.Value プロパティが返す値と同じであると考えられるため、x が null を表している場合を除けば、通常の整数同士の比較演算と同じです。