Nullable Type
ここでは、C#2.0から追加されることとなったNullable Typeについて書いています。 なおこの記事は2005年8月現在入手可能なVisual Studio 2005 Beta2及び、 C# Language Specifications 2.0を元にかかれています。
Nullable Typeが必要な状況とは?
まずはNullable Typeがどんなものなのかを見ていく前にNullable Typeがどのような 場合に必要になってくるのかを考えてみます。
例えば、整数型の配列を探索する関数(メソッド)を考えてみます。 通常、探索アルゴリズムを逐次探索とすれば、値が見つかった場合は そのキーを、見つからなかった場合は、-1を返す関数が思いつきます。 その例を以下に示します。
{
int[] arr = { 1, 2, 3, 4, 5 };
int result1 = Serch(arr, 3);
int result2 = Serch(arr, 10);
Console.WriteLine("'3'の探索結果:{0}", result1);
Console.WriteLine("'10'の探索結果:{0}", result2);
}
static int Serch(int[] target, int val)
{
for(int i = 0; i < target.Length; i++)
if (target[i] == val) return i;
return -1;
}
実行結果は次のようになります。
'10'の探索結果:-1
値が見つからなかった場合の戻り値
このプログラムは何の問題も無く動作します。しかし、値が見つからなかった時 返す-1とは一体何でしょうか?今回の場合キーの値としてとり得ない-1を探索できなかった時に返すということは、これまでも慣例的に行われて きたことなのでわかりますが、それは単なる暗黙の了解にすぎません。 本来プログラムとして、”何も無い”状態というのはnullで表現するべきです。
では-1を返す以外にどんな方法があるでしょうか。CLRではボックス化の機能がある ので、さきほどのSerchという関数の戻り値をobjectとすることで確かに null値を代入することができます。しかし、それではint型をobject型の変数に 代入した瞬間に型情報は失われて、タイプセーフではなくなります。当然 実際に値を取り出す時にはキャストも必要になってきます。
Nullable Typeによる解決
これらの問題をスマートに解決する手段として、Nullable Typeというものが C#2.0から加わることとなりました。これは簡単に言ってしまえば、 null値を代入することのできる値型のことです。 論より証拠ということでまずはNullable Typeを使ってさきほどの プログラムを書き換えてみます。
{
int[] arr = { 1, 2, 3, 4, 5 };
int? result1 = Serch(arr, 3);
int? result2 = Serch(arr, 10);
Console.WriteLine("'3'の探索結果:{0}", result1);
Console.WriteLine("'10'の探索結果:{0}", result2);
}
static int? Serch(int[] target, int val)
{
for (int i = 0; i < target.Length; i++)
if (target[i] == val) return i;
return null;
}
実行結果は次のようになります。
'10'の探索結果:
関数自体は戻り値の型がintからint?に変わっただけです。注目すべきは結果を 代入している変数result1とresult2です。この変数はやはりint?という型で宣言 されており、そこには'3'を探索した場合は2というint型の値が代入されています。 次に'10'という配列中には無い値を探索した場合はnull値が代入されています。 つまりint?という型は値型であるint型の値とnull値をとることができるのです。 これが、Nullable Typeです。?の前に置く事のできる型はNullable Typeでない値型 かstuructで制約された型パラメータに限られています。つまり、基本的な プリミティブ型(int, short, double...)は全てNullable Typeになり得るという ことです。
Nullable Typeの型変換について
さきほどの例ではint型が暗黙のうちにint?型になって戻り値として 返されていました。ではその逆、つまり、 int?からintへの変換はどうなるのでしょうか?また、double?からint?への変換などは できるのでしょうか?それらを確かめるために次のような 型変換の例を考えて見ます。
int? x = i;
double? y = x;
int? z = (int?)y;
int j = (int)z;
最初の型変換はintからint?への変換です。これは先ほどの例でもあったように 暗黙の変換が行われます。次のint?からdouble?への変換も特に明示的なキャストは 必要ありません。そして、double?からint?への変換ではキャストがされています。 ここまでの結果は容易に想像がつくと思います。なぜなら、これらはNullable Type ではない通常の値型においても同様の型変換の規則が存在するからです。しかし、最後の変換には注意が必要です。これはint?からintへの変換ですが、これには明示的な キャストが必要です。
bool?型について
先ほども書いたようにプリミティブ型はNullablet Typeになることができます。 ということは当然bool型もNullable Typeとして振舞うことができるわけです。 つまりbool?型の変数はtrue, falseに加えてnullという3つの値をとり得ることに なります。これはSQLの世界ではお馴染みの3値論理と呼ばれるものです。 これによって、SQLとの親和性は高まったといえるでしょうが、 あまり直感的とはいえない状況もでてくることでしょう。その真理値表を 以下に示します。
| x | y | x & y | x | y |
| true | true | true | true |
| true | false | false | true |
| true | null | null | true |
| false | true | false | true |
| false | false | false | false |
| false | null | false | null |
| null | true | null | true |
| null | false | false | null |
| null | null | null | null |
我々は2値論理に慣れているため、3値論理が直感的に理解できない部分がありますが、 すぐに慣れると思われます。
coalescing演算子
この演算子はC#2.0から新たに加わることとなった演算子です。これはある参照型 若しくはNullable Typeの変数がnull値でなければその値になり、 そうではなくnullであった場合は指定した値になるよう指示をする 演算子といえます。書き方は次のようになります。
この場合aがnull値でなければaの値になり、aがnull値であれば、bになります。 具体的な例はつぎのようになります。
int b = -1;
int x = a ?? b;
この場合aがnullであるためxにはbである-1が代入されることになります。 これは次のような三項演算子やif文を使わないですむので便利でしょう。
実際C#コンパイラは??演算子を三項演算子やif文に書き換えられる かたちに展開しています。因みにNullable Typeにある HasValueやGetValueOrDefaultについては名前から想像できる意味でいいと思います。
変更されたNullable Typeの実装
これまでNullable Typeを実際使用する場合に重点を置いて書いてきました。 では、このNullable Typeがどのように実現されているのでしょうか? 現在入手可能なVisual Studio 2005 Beta2における実装では ジェネリックを用いて実装されています。.NETにおいて、ジェネリックの インスタンス化は実行時に行われるのでNullable Typeをジェネリックで 実装した場合ボックス化は回避することができ、パフォーマンスの問題 は解消されます。しかし、Nullable Type自身をボックス化した場合は どうなるのでしょうか?Nullable Type自身は値型なので、実際にはnull値を とっているわけではありません。さきほどもでてきたようにHasValueという プロパティが単に値が入っている時true、そうでない時はfalseとしている だけです。従って、ボックス化した際には参照型の変数にはNullable Type のインスタンスが入っていることになり、nullではありません。そうすると、 Nullable Typeのインスタンスの時にはnull値を取っていたのに、ボックス化したら nullでは無いという状況が出てくることになります。実際以下の プログラムをBeta2で実行するとfalseという結果になります。
object o = x;
Console.WriteLine(o == null);
これは直感的にはあまり納得のいくものではありません。 従って、製品版ではこれが改良されることとなりました。 つまり、上記のコードを実行するとtrueになるようCLRに変更を加えたそうです。 今後はNullable Typeはジェネリックで実現されているように見えても 実際CLR上では特別な型として認識されるようになるようです。
このようにNullable Typeの実装方法は大きく変更されたので、 ここでBeta2の実装方法を見てもあまり意味は無いでしょう。 詳細はそれが明らかとなった時になると思われます。