連載:C# 2.0入門

第4回 Findメソッドとnull許容型

株式会社ピーデー 川俣 晶
2007/08/31

null許容型の内部構造

 null許容型は、実際にはNullable<T>構造体(System名前空間)の省略形である。つまり、「int?」は、「System.Nullable<int>」の短縮された表記だということである。

 Nullable<T>構造体は、HasValueプロパティとValueプロパティを持っている。HasValueプロパティは、値を持っていればtrue、持っていなければ……つまりnullならfalseになる。Valueプロパティは値を持っている場合のみ、その値そのものを提供する。

 しかし、これだけでは使いにくいので、C# 2.0コンパイラは複雑な自動型変換を挿入して対処する。

 例えば、リスト9の「a + b」という式はいとも自然に書かれ、素直に計算できるように思える。

using System;

class Program
{
  static void Main(string[] args)
  {
    int? a = 1;
    int b = 2;

    Console.WriteLine(a + b); // 出力:3
  }
}
リスト9 null許容型の値を加算に使った例

 しかし、「int?」の実体は「Nullable<int>」なので、足し算の対象にはできない。足し算の対象になる整数値は「その中に入っている」のである。しかも、null許容型ということは、nullだった場合の対処も必要となる。

 上記のリスト9をVisual Studio 2005でリリース・ビルド後、.NET ReflectorでC#として逆コンパイルすると何が起きているのかが見えてくる。

 「a + b」の式に相当する部分は、以下のようになっていた。

CS$0$0000.HasValue ? new int?(CS$0$0000.GetValueOrDefault() + CS$0$0001) : null

 つまり、まずnullであるかどうかが判定され、null値であれば計算は実行されることなく式の値はnullで確定する。そうでない場合は、内部に格納された値を使って計算されるわけである。

 一方、null許容型がnullか否かの判定も実は面倒である。以下のコードも一見普通に書かれているように見えるがそうではない。

using System;

class Program
{
  static void Main(string[] args)
  {
    int? a = null;

    if(a == null)
    {
      Console.WriteLine("aはnullです。");
    }
    // 出力:aはnullです。
  }
}
リスト10 null許容型がnullか否かの判定

 ここで「int?」の実体はNullable<int>構造体という“構造体”なので、もちろんnullになることはあり得ない。この値がnullであるかの判定は、実はHasValueプロパティを使うのが正しい。しかし、「HasValue」というキーワードは、機能を適切に示すよい名前ではあるが、nullであるか否かを判定するプロパティの名前だとは直感的に分かりにくい。

 そこでnull許容型では、「==」や「!=」でnullと値を比較する場合には、「あたかもnull許容型の値そのものがnullであるかのように」判定式を書くことが可能になっている。それが「if (a == null)」という部分の意味である。

 このリスト10は、リスト9と同様に.NET Reflectorで逆コンパイルすると、以下のようなコードが実際に生成されていることが分かる。

if (!a.HasValue)
「if (a == null)」の逆コンパイル結果

null合体演算子

 null許容型を使い始めると、どうしても「nullでない場合には」という条件判定を書く機会が増えてしまう。

 それをすべてif文で書いていては、コードが膨らんできりがない。

using System;

class Program
{
  static void Main(string[] args)
  {
    int? a = 123;

    if (a != null)
    {
      Console.WriteLine("Route {0}", a * 2);
    }
    else
    {
      Console.WriteLine("Route 0");
    }
    // 出力:Route 246
  }
}
リスト11 コードが伸びがちなif文による判定

 そこで、3項演算子を活用してコードをコンパクトに書きたくなる。

using System;

class Program
{
  static void Main(string[] args)
  {
    int? a = 123;

    Console.WriteLine(a != null ? a * 2 : 0); // 3項演算子の利用
    // 出力:246
  }
}
リスト12 3項演算子によるソースの短縮

 だが、「a != null ? a * 2 : 0」という式は冗長で分かりにくい。aは2回も出てくるし、本当に得たい値を計算する式「a * 2」よりも、条件式「a != null」の方が長い。毎回毎回「 != null」を書くと思うと、うんざりしてくるだろう。

 そこで、C# 2.0ではスペシャルな贈り物が用意されている。それが、「null合体演算子」である。これを使うと、リスト13のように式をさらに短くできる。

using System;

class Program
{
  static void Main(string[] args)
  {
    int? a = 123;

    Console.WriteLine("Route {0}", a * 2 ?? 0);
    // 出力:Route 246
  }
}
リスト13 null合体演算子(??演算子)によるコードの簡潔化

 このnull合体演算子は、「??」と表記するが、「&&」や「||」のような演算子と効能が似ている。つまり手前の式の値次第で、後の式を評価しないのである。

 具体的にいえば、「式1 ?? 式2」と書いたとき、これは以下のような値となる。

  • 式1の値がnullではないとき → 式1の値(式2は評価されない)
  • 式1の値がnullのとき → 式2の値

 つまり、「??」と書くだけで、いとも簡単にnullだった場合に補う値を記述できるわけである。これがあるとないとでは、null許容型の使い勝手が歴然と違ってくるだろう。

 それだけではない。

 null合体演算子は参照型にも使用可能なのである。例えば、リスト14はstring型に対して使用した例である。

using System;

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine(Console.ReadLine() ?? "EOF");
  }
}
リスト14 参照型でのnull合体演算子の利用

 このプログラムを実行し、何か1行入力して[Enter]キーを押した場合、Console.ReadLineメソッドは文字列オブジェクトを返す。それはnullではないので、そのままConsole.WriteLineメソッドの引数になり、画面に出力される。

 一方、[Ctrl]+[Z]キーを押して[Enter]キーを押すと、標準入力は入力が終了したEOF(End Of File)状態となる。このとき、Console.ReadLineメソッドはnullを返す。その結果、null合体演算子の2番目の式が評価され、結果として「EOF」という文字列が式の値となり、これが出力される。

 ちなみに余談だが、C系の言語では、3項演算子を宗教的に嫌うプログラマーも多いが、果たしてnull合体演算子も拒絶されるのだろうか。少し興味のある問題である。


 INDEX
  C# 2.0入門
  第4回 Findメソッドとnull許容型
    1.MATステートメントの思い出/前回に語り残したこと:ForEachメソッドのbreak問題
    2.ForEachだけではない繰り返しメソッド/複数の結果が欲しい場合/偉大なる前進とは何か?/そしてC# 3.0とLINQへ続く
    3.null許容型とは何か?/なぜnullを入れたいのか
  4.null許容型の内部構造/null合体演算子
    5.is演算子の挙動に注意/3値論理型として使用できるbool?型/nullを許容するとパフォーマンスに影響するか?/補足:null許容への批判
 
インデックス・ページヘ  「C# 2.0入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間