atsukanrockのブログ

Microsoft系技術を中心にぼちぼち更新します

Nullableはまあまあ賢い

はじめに

System.Nullable<T>は、まあまあ賢い。まあまあ賢いとはどういうことかというと、予想したとおり動作してくれる。具体的には、

  • nullとの比較
  • ボックス化

が、予想したとおり動作する。

サンプルコード

以下に、NUnit(バージョン2.5以上)でのテストコードを示す。このテストがパスする。

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

// x, y とも null
Assert.IsTrue(null == x); // null との == 演算子
Assert.IsFalse(null != x); // null との != 演算子
Assert.AreEqual(null, x); // null との Equals メソッド
Assert.IsNull(x);

Assert.IsTrue(x == y); // Nullable 同士の == 演算子
Assert.IsFalse(x != y); // Nullable 同士の != 演算子
Assert.AreEqual(x, y); // Nullable 同士の Equals メソッド

// null をボックス化
object obj = x;
Assert.IsNull(obj); // ボックス化結果が null
x = (int?)obj; // エラーなしでボックス化解除可能

// ボックス化解除時の注意 1
long? longX;
longX = (long?)obj; // obj が null だとボックス化解除可能

y = 1;

// x が null で y が not null
Assert.IsFalse(null == y); // null との == 演算子
Assert.IsFalse(x == y); // Nullable 同士の == 演算子
Assert.IsTrue(x != y); // Nullable 同士の != 演算子
Assert.AreNotEqual(x, y); // Nullable 同士の Equals メソッド

// not null をボックス化
obj = y;
Assert.IsNotNull(obj); // ボックス化結果が not null
y = (int?)obj;

// ボックス化解除時の注意 2
Assert.Throws(typeof(InvalidCastException),
    () => longX = (long?)obj); // obj が not null だとボックス化解除不可能
longX = (int?)obj; // これなら OK
longX = (int)obj; // これも OK

x = 1;

// x, y とも not null かつ Value が等しい
Assert.IsTrue(x == y); // Nullable 同士の == 演算子
Assert.IsFalse(x != y); // Nullable 同士の != 演算子
Assert.AreEqual(x, y); // Nullable 同士の Equals メソッド

x = 2;

// x, y とも not null かつ Value が等しくない
Assert.IsFalse(x == y); // Nullable 同士の == 演算子
Assert.IsTrue(x != y); // Nullable 同士の != 演算子
Assert.AreNotEqual(x, y); // Nullable 同士の Equals メソッド

サンプルコードの解説

nullとの比較については、注意すべき点は何もないと思う。

ボックス化については、少し注意が必要だ。

ボックス化

Nullableをボックス化した場合、ボックス化結果のobjectがどのような値となるか、Nullable<T>.HasValue値毎に整理すると、

  • falseの場合:object型の値null
    ※ボックス化前の型情報が、失われる
  • trueの場合:Nullable.Valueを値とするobject {T}型の値
    ※ボックス化前にNullableだったという情報が、失われる

となる。

ボックス化解除

ボックス化解除は原則、ボックス化前の型へのボックス化解除でないと成功しない。しかし、ボックス化解除後の型がNullableの場合、少し異なる。サンプルコード内「ボックス化解除時の注意 2」のあたりを参照されたい。

上記のボックス化の仕様により、int? x = 1としたxをボックス化した結果は、object {int}の値1だ。この時点でボックス化前の型がint?だった情報は失われている。このことと、上記のボックス化解除の原則を組み合わせて考えると、intへのボックス化解除しか成功しないことになる。しかし、実際にはint?へのボックス化解除も成功する。まぁ、そういうことなのだろう。

ちなみに、longやlong?へのボックス化解除は失敗する。これは、ボックス化解除の原則どおりなだけである。