Null条件演算子とNull合体演算子を理解しよう!

[C#] Null条件演算子とNull合体演算子を理解しよう!

※ 当サイトは広告を含みます。

ここではC#のnullに対する便利系の演算子、Null条件演算子Null合体演算子を学びます。
知らなくてもOKと言いたいとこですが、これ超便利です。僕は必須知識だと思ってます。

Null条件演算子

Null条件演算子とは、メソッド等にアクセスする際、対象がnullの場合でも良い感じに処理してくれる演算子です。
まずは次のサンプルコードを確認しましょう。これは普通にクラスのメソッドを呼んでるだけです。


namespace Sample
{
  internal class Program
  {
    internal class Character
    {
      public void Display()
      {
        Console.WriteLine($"りさ");
      }
    }

    static void Main(string[] args)
    {
      Character c = new Character();
      c.Display();
    }
  }
}

では、次はこちらのサンプルコードをどうぞ。これは参照値をnullにしたので例外が発生します。


namespace Sample
{
  internal class Program
  {
    internal class Character
    {
      public void Display()
      {
        Console.WriteLine($"りさ");
      }
    }

    static void Main(string[] args)
    {
      Character c = null; // nullにする
      c.Display();
    }
  }
}

これをNullReferenceExceptionが発生しないようなコードにする場合、普通は条件分岐で記述します。
なお、今回は例外を発生させたくないので、try-catchを使うパターンは駄目です。


namespace Sample
{
  internal class Program
  {
    internal class Character
    {
      public void Display()
      {
        Console.WriteLine($"りさ");
      }
    }

    static void Main(string[] args)
    {
      Character c = null; // nullにする

      // null判定
      if (c != null)
        c.Display();
    }
  }
}

この方法、呼び出し数が増えたり、メソッドからメソッドを呼ぶパターンになると、凄く手間です。
Null条件演算子ってのは、さっきのパターンを良い感じに記述できる演算子になります。

そして、先程のパターンをNull条件演算子に書き換えるとこうなります。


namespace Sample
{
  internal class Program
  {
    internal class Character
    {
      public void Display()
      {
        Console.WriteLine($"りさ");
      }
    }

    static void Main(string[] args)
    {
      // nullのパターン
      Character c1 = null; // null
      c1?.Display(); // Null条件演算子

      // nullでないパターン
      Character c2 = new Character(); // not null
      c2?.Display(); // Null条件演算子
    }
  }
}

なんと! Null条件演算子で記述すると、null参照しても例外が発生しません。
これを使うと、参照元がnull以外なら普通に実行、nullなら無視みたいに実行されます。

記述方法はシンプルで.(ドット)演算子の前に?(クエスチョンマーク)を記述します。


c1?.Display();

特に便利なパターンがメソッドやプロパティを繋げて記述する場合です。


namespace Sample
{
  internal class Program
  {
    internal class Data1
    {
      public Data2 Data2 { get; } = new Data2();
    }

    internal class Data2
    {
      public Data3 Data3 { get; } = new Data3();
    }

    internal class Data3
    {
      public void Display()
      {
        Console.WriteLine($"りさ");
      }
    }

    static void Main(string[] args)
    {
      var data1 = new Data1();
      data1?.Data2?.Data3?.Display(); // Null条件演算子
    }
  }
}
管理人

こんな感じにチェーンして記述した場合でも、Null条件演算子が良い感じに処理してくれます。

りさ

これってnullの場合はスキップされてるんですか?

管理人

そんな感じ。

先程のパターンでは短絡評価(ショートサーキット)と呼ばれる機能が働いてます。

これはNull条件演算子で記述した箇所の値がnullの場合、その時点で評価を止める機能です。
結果的にnull値以降の処理は実行されず、ぱっとみスキップされたような動作になります。

条件付き論理演算子の短絡評価と同じです。無意味なので後半がスキップされます。

では、プロパティや戻り値があるメソッドの場合の処理はどうなるのでしょうか?
この場合はシンプルにnull参照が発生した時点で評価を終了、そのままnullが返ります。


namespace Sample
{
  internal class Program
  {
    internal class Data1
    {
      public Data2 Data2 { get; } = new Data2();
    }

    internal class Data2
    {
      public Data3 Data3 { get; } = null;
    }

    internal class Data3
    {
      public string Value { get; } = "りさ";
    }

    static void Main(string[] args)
    {
      var data1 = new Data1();

      var v = data1?.Data2?.Data3?.Value; // Null条件演算子
      Console.WriteLine($"{v == null}");
    }
  }
}

このnullが返る都合から、戻り値の型はNullable型になります。


namespace Sample
{
  internal class Program
  {
    internal class Data
    {
      public int Value { get; } = 128;
    }

    static void Main(string[] args)
    {
      var d = new Data();

      int? v = d?.Value; // Nullable型
      if (v.HasValue)
        Console.WriteLine($"{v.Value}");
    }
  }
}
管理人

利用時に型を間違えるとビルドエラーになるので注意しましょう。

また、Null条件演算子インデクサーでも利用できます。
この場合は[]の前に?を記述して?[]って感じに書きます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      string[] array = null;

      Console.WriteLine($"{array?[0]}"); // []にも利用可能
      Console.WriteLine($"{array?[1]}"); //  〃
      Console.WriteLine($"{array?[2]}"); //  〃
    }
  }
}

なお、これはnullを回避する機能であって、範囲外を回避する機能ではありません。
仮に次のような記述をしても、IndexOutOfRangeExceptionは普通に発生します。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      string[] array = new string[]
      {
        "ABC",
        "DEF",
        "GHI",
      };

      // 範囲外にアクセス
      Console.WriteLine($"{array?[10]}"); // IndexOutOfRangeException発生
    }
  }
}

Null合体演算子

これもnullに関する便利系の演算子です。Null条件演算子と同じく、僕は必須知識だと思ってます。
こちらは、対象がnull以外なら対象自体を返し、nullなら任意の値を返せる演算子です。

まずはサンプルコードを確認しましょう。ぱっとみ三項演算子と似てます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      string s = null;
      Console.WriteLine($"{s ?? "nullだよ"}"); // Null合体演算子
    }
  }
}

Null合体演算子??(クエスチョンマーク2個)を使って記述します。


対象 ?? nullの場合に返す値

また、??演算子右側は対象と同一の型である必要があります。
仮に対象が文字列なら、次のように記述します。もちろん変数とかもOK。


s ?? "nullだよ"

よく使うのは値の代入でnullなら補正するような記述です。


namespace Sample
{
  internal class Program
  {
    public class Data
    {
      public string Name { get; set; }
    }

    static void Main(string[] args)
    {
      string inputName = null;

      var data = new Data()
      {
        Name = inputName ?? "名無しさん", // Null合体演算子
      };

      Console.WriteLine($"{data.Name}");
    }
  }
}

Null合体演算子を使わない場合は三項演算子とか条件分岐で記述します。
三項演算子を使ったパターンならこんな感じ。ちょっと余計なコードが増える。


namespace Sample
{
  internal class Program
  {
    public class Data
    {
      public string Name { get; set; }
    }

    static void Main(string[] args)
    {
      string inputName = null;

      var data = new Data()
      {
        Name = inputName != null ? inputName : "名無しさん", // 三項演算子で記述する
      };

      Console.WriteLine($"{data.Name}");
    }
  }
}

また、複合代入にも対応していて、??=と記述すれば同時に代入ができます。
+=とか-=を使う感覚と同じです。条件式を使わなくてもnull値を1行で補正できます。


namespace Sample
{
  internal class Program
  {
    public class Data
    {
      public string Name { get; set; }
    }

    static void Main(string[] args)
    {
      string name = null;

      // Null合体演算子の複合代入
      name ??= "名無しさん";

      Console.WriteLine($"{name}");
    }
  }
}

他にも型さえ正しければ割りと色々な場所で記述できます。
例えばforeach利用時に配列がnullなら空配列に補正するとか。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int[] array = null;

      foreach (var a in array ?? new int[] { }) // Null合体演算子
        Console.WriteLine($"{a}");
    }
  }
}

後はnull値の場合に例外を投げるような記述もできます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int[] array = null;

      foreach (var a in array ?? throw new ArgumentException()) // Null合体演算子を利用して例外を投げる
        Console.WriteLine($"{a}");
    }
  }
}
管理人

いろんな場所で使えて超便利!

あとがき

条件分岐を記述しなくていいから、絶対1行で記述するマンには超オススメ。
特にコンストラクタでプロパティに直代入する場合とか最強の見た目になる。

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。