変数名の後ろに"!" がある - null 免除演算子の利用 - C#

C#のコードで、変数名の後ろに!記号がある場合の意味を紹介します。

概要

変数名の後ろの ! 記号はnull免除演算子(null-forgiving operator)と呼ばれます。

null免除演算子

null免除演算子 ! をnull許容参照型の変数名の後ろに記述すると、その変数は null でないとみなされます。

例1

UI

下図のフォームを作成します。

コード

下記コードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace NullForgivingDemo
{
  public partial class FormSimpleNullForgiving : Form
  {
    public FormSimpleNullForgiving()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      string str = "ぺんぎんクッキー";
      string? newstr = "かるがもタルト";
      if (new Random().NextDouble() < 0.5) newstr = default;
      str = newstr;
      textBox1.Text = str;
    }
  }
}

ビルド

ビルドを実行すると、下記のワーニングが発生します。
メッセージ
CS8600:Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。

解説

確率でstring型のstr変数にnull許容型のstring型のdefault値を代入する処理です。string?型のdefault値はnullとなるため、string型にnullを代入してしまう場合があります。
このため、コンパイル時に先のワーニングが発生します。
  string? newstr = "かるがもタルト";
  if (new Random().NextDouble() < 0.5) newstr = default;
  str = newstr;

null 免除演算子を利用したワーニングの回避方法

以下のコードに修正すると、ワーニングが発生しなくなります。
    private void button1_Click(object sender, EventArgs e)
    {
      string str = "ぺんぎんクッキー";
      string? newstr = "かるがもタルト";
      if (new Random().NextDouble() < 0.5) newstr = default;
      str = newstr!;
      textBox1.Text = str;
    }

解説

newstr! の記述により、newstr変数はnullでないとみなされます。そのため、ワーニングは発生しなくなります。 一方で実行時の動作は変わらないため、元のコードと動作は全く同じになります。
  str = newstr!;

実行結果

プロジェクトをビルドすると、上記のアラートは発生しないことが確認できます。

プロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。下部のテキストボックスに「かるがもタルト」の文字列が表示されるか、テキストボックスが空白になるかの動作をします。 乱数による動作のため、クリックするごとにどちらかの表示になります。


補足1

なお、nullが代入される状況が発生しない下記のコードではワーニングは発生しません。
    private void button2_Click(object sender, EventArgs e)
    {
      string str = "ぺんぎんクッキー";
      string? newstr = "かるがもタルト";
      str = newstr;
      textBox1.Text = str;
    }

補足2

null 許容値型の場合は、!を記述してもビルドエラーになります。
    private void button3_Click(object sender, EventArgs e)
    {
      int value = 100;
      int? newvalue = 56;

      if (new Random().NextDouble() < 0.5) newvalue = null;

      value = newvalue!;
      textBox1.Text = value.ToString();
    }

次のエラーが発生します。
エラーメッセージ
CS0266: 型 'int?' を 'int' に暗黙的に変換できません。明示的な変換が存在します (cast が不足していないかどうかを確認してください)


例2

次のプログラムを作成します。

UI


コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace NullForgivingDemo
{
  public partial class FormSimpleNullForgiving : Form
  {
    class MyItem
    {
      public int id = -1;
      public string name = "ぺんぎんクッキー";
    }

    public FormSimpleNullForgiving()
    {
      InitializeComponent();
    }

    private void button4_Click(object sender, EventArgs e)
    {
      MyItem? item = null;
      textBox1.Text = String.Format("名前 : {0}", item.name);
    }
  }
}

ビルド

ビルドを実行すると、下記のワーニングが発生します。
メッセージ
CS8602: null 参照の可能性があるものの逆参照です。

解説

nullになるMyItemクラスのメンバ変数を参照する処理です。MyItem? 型のitem変数はnullとなるため、item.nameは NullPointerExceptionとなり、コンパイル時に先のワーニングが発生します。
  MyItem? item = null;
  textBox1.Text = String.Format("名前 : {0}", item.name);

null 免除演算子を利用したワーニングの回避方法

以下のコードに修正すると、ワーニングが発生しなくなります。
    private void button4_Click(object sender, EventArgs e)
    {
      MyItem? item = null;
      textBox1.Text = String.Format("名前 : {0}", item!.name);
    }

実行結果

プロジェクトをビルドして実行し、button4をクリックします。nullのメンバ変数にアクセスしたため、例外が発生します。
下記例外が発生します。
エラーメッセージ
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=NullForgivingDemo
! null 免除演算子を記述してもワーニングの発生を抑えられるだけで、nullのオブジェクトにアクセスしたことによる例外は回避できません。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2022-03-04
iPentec all rights reserverd.