連載
» 2016年07月07日 05時00分 UPDATE

.NET TIPS:構文:nullチェックを簡潔に記述するには?[C# 6.0]

C# 6で追加されたNull条件演算子(?./?[演算子)を使うと、これまではif文などで行っていた「nullチェック+何らかの処理」を簡潔に記述できるようになる。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載目次

対象:Visual Studio 2015(C# 6.0)以降


 あるオブジェクトのメソッドなどを呼び出すとき、それがnullではないと確信できない場合はnullをチェックするコードを書かねばならない。いちいちnullを判定するif文を書くのは面倒だと思ったことはないだろうか? C# 6.0で導入されたNull条件演算子を使えば、簡潔に記述できるのだ。本稿ではその使い方を説明する。

Null条件演算子の基本

 Null条件演算子は、オブジェクトのメソッドやプロパティなどのメンバを呼び出す「.」記号、あるいは、インデクサーを呼び出す「[]」記号の手前に「?」記号を書く。その働きは、オブジェクトがnullのときには後続のメンバ呼び出し/インデクサー呼び出しを実行せずにnullを返すというものである。

 例えば次のコードのように使う。

using static System.Console;
class Program
{
  static void Main(string[] args)
  {
    int? n = null;
    // var result1 = n.ToString();
    // ↑これはSystem.NullReferenceExceptionになる

    // Null条件演算子
    var result1 = n?.ToString(); // result1にはnullが入る
    // var result1 = n ? . ToString(); // 「?」の前後に空白を入れてもOK
    var output1 = result1 ?? "(null)";
    WriteLine($"result1={output1}");
    // ⇒result1=(null)

    // 従来の書き方
    string result2 = null;
    if (n != null)
      result2 = n.ToString();
    var output2 = result2 ?? "(null)";
    WriteLine($"result2={output2}");
    // ⇒result2=(null)

#if DEBUG
    ReadKey();
#endif
  }
}

Null条件演算子を使ったコンソールアプリの例
変数「n」はint?型(=nullを許容するint型)だ。変数「n」をnullにしているので、そのままToStringメソッドを呼び出すとSystem.NullReferenceException例外が発生してしまう。これまでであれば、「従来の書き方」とコメントしたようなnullチェックをするif文(または以下で示すような三項演算子を用いた式)を書くしかなかった。Null条件演算子を使えば、if文が不要になるのだ。
なお、型名なしでWriteLineメソッドを呼び出す書き方については、「.NET TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]」を、また、文字列の先頭に「$」記号を付ける書き方は、「.NET TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]」をご覧いただきたい。
また、「??」記号(Null合体演算子)についてはMSDNを見てほしい。

Null条件演算子の評価結果

 Null条件演算子も演算子であるから、評価結果を返す。それは、メンバ呼び出し/インデクサー呼び出しの結果であるか、nullである。次のコードのような条件演算子(三項演算子)の糖衣構文だと考えると分かりやすいだろう。

private int? n = null; // メンバ変数

……省略……

// Null条件演算子
var result1 = n?.ToString();

// 三項演算子でも同じ結果が得られる
var result3 = n.HasValue ? n.ToString() : null;

Null条件演算子は、三項演算子の動作に似ている

 ただし、三項演算子とは異なり、Null条件演算子はスレッドセーフである。

 上の三項演算子の例で説明すると、「n.HasValue」を評価した時点で「n」がnullでなかったとしても、「n.ToString()」を評価するまでの間に別スレッドからメンバ変数「n」の値をnullに書き換えられる可能性がある。もしもそのタイミングで書き換えられた場合には、「n.ToString()」を実行するときに(「n」がnullに変わっているので)例外が発生してしまう。上のような三項演算子の使い方は、スレッドセーフではないのだ。それに対して、Null条件演算子はスレッドセーフになっている。

 また、連続したNull条件演算子は、ショートサーキットする。例えば次のコードで、最初のNull条件演算子(「?[0]」の部分)がnullと評価された場合(listコレクションがnullの場合)、後続のNull条件演算子(「?.ToString()」の部分。先頭要素がnullかどうか+nullでないときには文字列化)は評価されない(=実行されない)のだ。

// Sample1メソッド:
// コレクション「list」の先頭要素を取り出し、文字列にして返す
// ただし、「list」がnullのときは文字列"(null)"を返す
// また、「list」の先頭要素がnullのときも文字列"(null)"を返す
static string Sample1(List<int?> list)
{
  return list?[0]?.ToString() ?? "(null)";
}

// 【参考】従来の書き方
static string Sample1_OldStyle(List<int?> list)
{
  if (list == null || list[0] == null)
    return "(null)";

  return list[0].ToString();
}

Null条件演算子はショートサーキットする
太字にした部分で、「list?[0]」がnullだった場合、後続の「?.ToString()」は評価されることなく、「list?[0]?.ToString()」の値がnullに確定する。
なお、Null条件演算子の連続している部分がショートサーキットされるだけなので注意してほしい。この例では、引数「list」がnullだったらメソッドの返値がnullになるというわけではない(「"(null)"」という文字列になる)。

スレッドセーフであることを利用する

 スレッドセーフが問題になるとき、if文でnullチェックする場合にはそのオブジェクトをキャッシュしておく必要がある(次のコード)。

public event PropertyChangedEventHandler PropertyChanged;

// PropertyChangedイベントを発火させるメソッド
protected void OnPropertyChanged(
                [CallerMemberName] string propertyName = null)
{
  // 従来の書き方:C#ではいったん変数にキャッシュする必要がある
  var eventHandler = PropertyChanged;
  if (eventHandler != null)
  {
    // ここのタイミングで別スレッドからPropertyChanged変数をnullにされても
    // 問題が起きないように、「eventHandler」変数に代入している
    eventHandler(this, new PropertyChangedEventArgs(propertyName));
  }
}

PropertyChangedイベントを発火させるメソッドの例(従来の書き方)
これはINotifyPropertyChangedインタフェース(System.ComponentModel名前空間)を実装するクラスの一部である。
メンバ変数であるPropertyChangedイベントは、null判定してから実際に呼び出すまでの間に別スレッドからnullに変えられる可能性があるので、このように「eventHandler」変数にいったんキャッシュしておく必要がある。そうしておけば、「PropertyChanged」変数がnullに変えられても、そこに入っていたオブジェクトは「eventHandler」変数に引き続き保持されているからだ。
なお、Visual BasicのRaiseEventステートメントでは、nullチェックは不要である。

 このような場合でも、Null条件演算子を使えば次のコードのようにすっきりと書ける。

public event PropertyChangedEventHandler PropertyChanged;

// PropertyChangedイベントを発火させるメソッド
protected void OnPropertyChanged(
                [CallerMemberName] string propertyName = null)
{
  // Null条件演算子を使う(スレッドセーフ)
  PropertyChanged?.Invoke(this,
                          new PropertyChangedEventArgs(propertyName));
}

PropertyChangedイベントを発火させるメソッドの例(Null条件演算子を使う)
これはINotifyPropertyChangedインタフェース(System.ComponentModel名前空間)を実装するクラスの一部である。
Null条件演算子はスレッドセーフなので、「PropertyChanged」変数をキャッシュしておく必要がない。Visual BasicのRaiseEventステートメントと同様に、イベントを発火させるコードが1行で書けるのだ。

まとめ

 Null条件演算子を使うと、nullチェックをしているコードが簡潔に書ける。三項演算子と違ってスレッドセーフであるのも使い勝手がよい。

「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。