Unity 2020.2からC#のバージョンがあがり、C#をnullセーフで書くことができます。
有効にするには
using文とclass宣言の前にnullセーフを有効にする特別なフラグを書きます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#nullable enable
public class Hoge : MonoBehaviour
{
}
nullセーフとは
nullセーフだったりnullセーフティだったりnull安全だったりと若干表記揺れがありますが、ざっくりいうとオブジェクトを格納している変数がnullになることがあるのかを明示できる言語の機能です。
これがあると、いわゆるヌルポになる原因をより簡単に見つけられるようになります。
C#でnullセーフを有効にするとオブジェクトを格納する変数は原則nullの代入を許しません。そのような箇所があるとビルド時に警告が表示されます。
// 警告
List<DateTime> date = null;
これは今までのC#ではできたことなのでかなりの破壊的変更になります。なのでエラーではなく警告止まりなのだと思います。ですが、これがあることによりif文でnullチェックをする必要がほとんどの場合でなくなります。
ただいくつかの場合どうしてもnullにしなければならないものや、なってしまうものもあります。
例えば循環参照を断ち切るためにOnDestroyでnullを代入することがあります。
PlayerとEnemyがそれぞれに攻撃し合う処理をお互いのクラスの中で書く場合、PlayerはEnemyを、EnemyはPlayerを参照する必要があります。
どちらかが負けてフィールドから消滅する場合、OnDestroyのタイミングで循環参照を切る必要があります。
public class Enemy : MonoBehaviour
{
public Player player;
private void OnDestroy()
{
player = null;
}
}
しかしこのコードはnullセーフでは警告となります。変数playerはnullになってはならないからです。これを回避するためには2種類の方法があります。
Optional型
1つめはnullになることがあることを示すオプショナル型にします。
private Player? player;
?をつけると変数にnullを代入できます。でもこれではnullセーフを導入する意味はないのでは?と思ったかもしれませんが、nullになる可能性があるとわかったので、変数にアクセスする前にnullチェックをしなければならないのをコンパイル時に検証することができます。
// 警告
player.hp -= atk;
// OK
if (player != null)
{
player.hp -= atk;
}
// OK
player = null;
オプショナル型は処理の途中でnullになる可能性がある変数につけるとヌルポ対策にとても効きます。
Force null
もう一つは強制的にnullを代入する方法です。
[SerializeField]
private Player player = null!;
nullの後ろに!をつけるとnullセーフ型でもnullで初期化できます。
// OK
player.hp -= atk;
// 警告
player = null;
// OK
player = null!;
こちらは変数の初期化時にはどうしてもnullになってしまうが、処理を始める前には必ず値が入っているので毎回nullチェックをするのは面倒というシチュエーションの時に使います。
例のようにSerializeFieldでUnityが値を入れてくれる箇所に使うとnullチェック不要で変数にアクセスできます。ただC#的にはUnityが入れるまではnullであることは変わりないので、このように書く必要があります。
まとめ
どうしてもnullにせざるをえない箇所は例外的な書き方をしつつ、基本はnullを許容しないように書くことでバグを大幅に減らすことができます。nullセーフの機能は1ファイル毎に有効/無効にできるので導入できそうなところかできるというのも嬉しいですね。
余談
ちなみにiOSのSwiftでもSwiftUI以前は
private weak var UILabel! label
のようにシステムで後から値を入れてくれる箇所は!をつけていたのでnullセーフ言語でこのような回避方法はどうなのか?についてはあまり気にしなくていいと思います。