Nullable型(null許容値型)を理解しよう!

[C#] Nullable型(null許容値型)を理解しよう!

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

C#には少し特殊な型でNullable型と呼ばれる型が存在します。
値型と一緒に利用する特殊な型で、知っておくと便利系な知識です。

Nullable型(null許容値型)

これは値型を拡張してnull値の代入を可能にする仕組みです。
こんなのをイメージしてください。値型をNullable型で包み込む感じです。

Nullable型

こうやって元の値型の機能にNullable型の機能を付加します。
なので、1段階経由して値型にアクセスしてる感じですね。

管理人
管理人

あくまでイメージの話ね。後、普通の値型と比べてパフォーマンスが落ちます。

りさ
りさ

気にするほど?

管理人
管理人

いや、どうせ誤差だよ。

宣言方法

値型に?を付けて宣言するとNullable型になります。
Nullable型になると通常の値以外にnullも代入できるようになります。


int? x = null;

以下はサンプルコードです。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // Nullable型
      int? x = null; // nullを代入
      int? y = 100;  // 数値を代入

      Console.WriteLine($"x={x}, y={y}");
    }
  }
}
りさ
りさ

これ何に使うんですか?

管理人
管理人

値型だけど「Default値は未定義が良いなぁ」って時とか。

値型はDefault値で初期化されるため未定義がありえません。
例としてint型Default値0なので、少なくとも0という値が入ってます。

よくあるパターンでint型-1を未定義として扱い、正数をまともな値として利用することがあります。
そんな時、「冷静にそれ違くね?」って思ったらNullable型がベストだったりします。
つまり、値型だけど値が存在しない場合を表現したい時に使うのがオススメです。

HasValueプロパティ

英語のままで値の存在有無を取得できます。型はbool型です。
つまり、この値を確認すればnullorを判断できます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // Nullable型
      int? x = null; // nullを代入
      int? y = 100;  // 数値を代入

      Console.WriteLine($"x.HasValue={x.HasValue}, y.HasValue={y.HasValue}");
    }
  }
}

また、HasValueを直接比較してもOKですが、実はNullable型を直接null判断しても動作します。
以下がサンプルコードです。if文is演算子で直接比較した場合でも結果は同じになります。


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

      // HasValue
      if (x.HasValue)
        Console.WriteLine("x is not null");
      else
        Console.WriteLine("x is null");

      // null比較
      if (x != null)
        Console.WriteLine("x is not null");
      else
        Console.WriteLine("x is null");

      // is演算子
      if (x is not null)
        Console.WriteLine("x is not null");
      else
        Console.WriteLine("x is null");
    }
  }
}

Valueプロパティ

これも英語のままです。Nullable型にした値型の、値自体を取得できます。
ただし、null状態でアクセスするとInvalidOperationExceptionが発生します。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // Nullable型
      int? x = null; // nullを代入
      int? y = 100;  // 数値を代入

      try
      {
        // nullは例外が発生する
        Console.WriteLine($"x.Value={x.Value}");
      }
      catch (Exception e)
      {
        Console.WriteLine($"{e.Message}");
      }

      // null以外なら値が取得できる
      Console.WriteLine($"y.Value={y.Value}");
    }
  }
}

これもHasValueと同じで、直接アクセスすることができます。
以下がサンプルコードです。最初に例として紹介したコードと同じです。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // Nullable型
      int? x = null; // nullを代入
      int? y = 100;  // 数値を代入

      Console.WriteLine($"x={x}, y={y}");
    }
  }
}

また、is演算子を併用すれば、値が存在する場合だけ処理するような記述ができます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int? x = 10;

      // 値があるときだけ処理する
      if (x is var v)
        Console.WriteLine($"{v}");
    }
  }
}

Valueプロパティを経由しない場合の注意ですが、あくまで型はNullable型です。
忘れがちになりますが、次のような記述はもちろん無理です。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int? x = 10;

      // 型が異なるのでNG
      Method(x);
    }

    static void Method(int v)
    {
      Console.WriteLine($"{v}");
    }
  }
}

GetValueOrDefault()メソッド

これはNullable型nullなら値型のDefault値null以外なら所有する値を取得するメソッドです。
稀の稀に利用することがあるかもしれません。僕は経験上、殆ど使った記憶がないです。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int? x = null;
      int? y = 100;

      Console.WriteLine($"x.GetValueOrDefault()={x.GetValueOrDefault()}, y.GetValueOrDefault()={y.GetValueOrDefault()}");
    }
  }
}
管理人
管理人

Nullable型nullであること意味があるので、これを使うようなら値型に戻したほうがいいよ。

リフト演算子

これはNullable型に対して単項演算子(++--等)2項演算子(+とか-等)を利用した場合に機能します。
先に言ってしまうと、Nullable型nullの状態で演算しても、良い感じに処理して大体nullになる演算子です。

大体と言ったとおり、時々違います。特にbool型がスーパー迷惑で分かりづらいです。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int? x = 10;
      int? y = 25;
      int? z = null;

      Console.WriteLine($"x + y = {x + y}");
      Console.WriteLine($"x + z = {x + z}");

      Console.WriteLine($"x++ = {++x}");
      Console.WriteLine($"z++ = {++z}");

      bool? a = null;

      Console.WriteLine($"a & true  = {a & true}");
      Console.WriteLine($"a & false = {a & false}");
      Console.WriteLine($"a | true  = {a | true}");
      Console.WriteLine($"a | false = {a | false}");
    }
  }
}
管理人
管理人

正直、これは覚えなくていいです。むしろ間違いの元だから使わないべき。

りさ
りさ

ぱっとみ分かりづらいね。

特に2項演算子の場合ですが、そもそも普通の値型Nullable型を直接演算するのをやめましょう。
仮に演算したい場合はValueプロパティを経由します。もしくはis演算子nullを捌きます。
そうすればnull演算は発生しないため、先程のリフト演算子の挙動を気にする必要がなくなります。

あとがき

正負の数値を扱うけど未定義もありえるとか、そんな時はNullable型が向いてます。
後はNull条件演算子とか、少し特殊な演算子と一緒に利用することが多いですね。

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

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

関連記事

コメント

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