NetFramework では、null代入可能な値型をNullable<T>型によって表します。T?と略記することができます

一方、参照型ではもともとnull代入可能なので、逆にnullが代入できないことを表す型がなく、実行時にNullReferenceExceptionが発生して初めてミスに気付くことが度々あります。

これを解決するためC# 8.0ではnull許容参照型が導入されました。

記法はnull許容値型と同じく、型名の後に?を付けます。

但し、新たに型が追加されたわけではなく、同じ型についてnullが許容されるかどうかを判別する仕組みで動作します。NullReferenceExceptionが発生する可能性があると思われる場所には、コンパイル時に警告が出るようになっています。

#nullable enable

namespace NullableReferenceTypeTest
{
  class Test { }

  class Container<T>
  {
    public T? Value { get; set; }

    public Container(T? value)
    {
      Value = value;
    }
  }

  class Client
  {
    public T? GetValue<T>(Container<T?> container)
    {
      return container.Value;
    }

    public void GetValueTest()
    {
      var c1 = new Container<Test>(new Test());
      var v1 = GetValue(c1); //警告発生
      var c2 = new Container<Test?>(null);
      var v2 = GetValue(c2); //警告が発生しない
    }
  }
}

C# 9.0 では制約がない型引数に ? をつけることができるようになりました。

ジェネリックで制約がない型引数を使ってnull許容かどうかにかかわらず使えるクラスを作成する場合、型引数に ? をつけると問題が発生します。

右のコードはこのような場合に余分な警告が発生することを示すものです。 Container<T> はnull許容かどうかにかかわらず使うことを意図したクラスです。 GetValueTest では c1 にnull許容しない型Testを実引数Tに設定しています。 それに対してc2にはnull許容する型Test?を実引数Tに設定しています。 それぞれGetValueで値を取得しているのですが、null許容しない型c1を引数に指定した場合は警告が出ます。

#nullable enableは、以降でnull許容参照型を有効にすることを示しています。 同様に#nullable disableで無効になり、#nullable restoreで以前の設定に戻ります。

対策としては ! をつけて警告を無視させることも考えられます。

しかし、null許容かどうかにかかわらず使えるクラスの型引数にnull許容参照型を指定することは、上記のあいまいさがあるので、当面これに該当する部分はnull許容参照型を無効のままにしようと考えています。

ジェネリックでない型や、ジェネリックの場合でもnullを許容するかどうか用途が決まっている場合には、null許容参照型を使っていこうと思います。