【Unity】Null伝搬(条件)演算子とUNT0008

こんにちは！
株式会社OGIXのエンジニアのK.M.です。
（弊社については最後に紹介があるのでぜひ見てください）

**UNT0008 Null propagation on Unity objects**
https://github.com/microsoft/Microsoft.Unity.Analyzers/blob/main/doc/UNT0008.md

Unity C#でコードを書いていると、このような警告が出ることがあります。今回はこの警告について少し深掘りしてみましょう。
まず、この警告の翻訳的な意味ですが、

:::note warn
UnityオブジェクトでNull伝搬が使われている
:::
という警告になります。（これだと何が言いたいのかよく分かりませんよね）

「Null伝搬(Null propagation)」？？？あまり聞きなれない言葉ですが、Propagationの意味は、**伝搬** です。何かの情報を広く伝えていくことを意味します。警告の直訳は上記のとおりなのですが、意味としては、Unityオブジェクトに対してNull伝搬演算子、あるいはその他のNull関連演算子を**使用してくれるな**、という意味です。

:::note info
「プロパガンダ」という言葉がありますが、これは政治的に偏った情報を広く国民に伝えていくことを意味します。同じ語源ですね。
:::
:::note info
「Null伝搬演算子」は「Null条件演算子」とも呼ばれていて「Null条件演算子」の方がポピュラーな呼び方のような気がするのですが、Unityの警告では、「Null伝搬」という呼び方をしているので注意が必要です。演算子の表記としては「?.」あるいは「?[]」です。UNT0008が発生するのは、「?.」を使用した場合です。（null合体演算子によるメソッドの呼び出し「?.()」は「?.」に含めて考えて問題ないでしょう。）
:::

# 具体例

UNT0008が発生する具体例を見てみましょう。
例えば、EnemyというMonoBehaviourを継承したクラスがあり、そのインスタンスenemyのtransformを取得したいとします。

```C#
Enemy enemy;
......
var t = enemy.transform;
```

というコードになりますが、もしenemyがすでにDestroyされている場合は、enemyはnullになってしまうので、Null参照の例外が発生する危険性があります。
この例外発生を回避するために
```C#:UNT0008発生例
var t = enemy?.transform;
```
というコードを書いたりするわけですが、このコードではUNT0008の警告が出ます。

:::note warn
enemyがnullだったときは、そのnullが伝搬して、tがnullになるけど、
Unityオブジェクトではやめてくれ
:::
っていう意味の警告が出るのです。

じゃ、警告を消すためにはどうすれば良いかというと、

```C#:UNT0008を回避
Transform t = null;
if ( enmey != null ) {
   t = enemy.transform;
}
```

このコードなら警告は出ません。一見同じ意味のコードの様に見えますが、Unityにとっては違う意味を持っています。

# Unityにおけるnull比較

Unityオブジェクトの比較演算子「==」はオーバーライドされていて特殊な判定が行われるようになっています。それはオブジェクトをDestroyした後に参照がnullかどうかを比較した場合、Destroy直後からnullとして判別できるようにオーバーライドされているわけですね。

```C#:Destroy直後にnull判定する
Enemy enemy;

......

DestroyImmediate( enemy );
if ( enemy == null ) {
   Debug.Log("nullです");
}
```

上記のコードでは、「nullです」がログに表示されます。

Destroyが呼ばれてから実際にメモリ中から削除されれるまでに少し時間がかかるのでその間も削除されていることが判別できるように（不正なアクセスを起こさないという意味合いの方が強いかな）「==」演算子をオーバーライドして判定できるようにしているわけですね。便利な仕組みなのですが、上記のnull伝搬演算子「?」では、「==」演算子が呼ばれないという問題が発生してしまっています。

:::note info
null伝搬演算子「?」では、「==」演算子ではなく、参照を直接nullと比較している(ReferenceEqualsが呼ばれている)と推測できます。すでにDestroyされているオブジェクトが「null伝搬」演算子を使用するとそのせいで不正なアクセスが起こってしまう可能性があるので、Unityが警告を発生させているのです。ここは素直に警告に従って書き換えるようにしましょう。
:::

# その他のnull関連演算子
### Null合体演算子
C#には、その他のNull関係の演算子として、「Null合体演算子」というものがあります。
「??」と「??=」演算子で、「??」演算子は、左の値がnullでない場合は、??の左の値を返し、nullの場合は右の値を返すというものです。

```C#:Null合体演算子の例
var obj = parent ?? defaultParent;
```
`parentがnullでないなら、parentを使用し、nullの場合は、defaultParentを使用する`という意味です。
<br>

同じ意味でこのような書き方もできます。

```C#:Null合体代入演算子の例
var obj = parent;
obj ??= defaultParent;
```

`objをparentで初期化。objがnullでないなら、objはparentのまま、nullの場合は、defaultParentで上書きされる`、という意味ですね。

ここで使われている「??」,「??=」演算子も、「==」演算子は呼ばれないので、parentがUnityオブジェクトである場合は、UNT0008警告がでます。「==」演算子を使った書き方に書き換えましょう。

### パターンマッチングによるnull比較
その他、パターンマッチングを使った「is null」でnullかどうかを比較することもできますが、こちらに対してもUNT0008が出ますので、「== null」に書き換えましょう。

:::note info
パターンマッチングを使うとこのように変数宣言と代入と比較を1行で書くことができて便利なのですが、Unityオブジェクトの場合はUNT0008が出るので、こちらの場合も素直な書き方にしましょう。

```C#:パターンマッチングを使った例
if ( SearchItem() is var item and not null ) {
   // itemが見つかったので使う
   ....
}
```
`if ( SearchItem() is var item and != null )`とは書けない。
:::

# まとめ
* Unityオブジェクトには、null関係の演算子は使わない。必ず「== null」あるいは「!= null」で比較をする
* Unityオブジェクトの参照比較はオーバーライドされていることを常に留意する
* UNT0008の警告が出た場合は、見逃さずに書き換える
* 直接nullと比較したい場合は、「is null」あるいは、「ReferenceEquals」を使うことができる

# 一緒に働く仲間を募集しています！
株式会社OGIXでは一緒に働いてくれる仲間を募集しています！
エンタメ制作集団としてゲームのみならず、未来を見据えたエンタメコンテンツの開発を行っています。

事業拡大に伴い、エンジニアさんを大募集しています。
興味のある方は下記リンクから弊社のことをぜひ知っていただき応募してもらえると嬉しいです。
▼会社について
https://www.wantedly.com/companies/company_6473754/about
▼代表インタビュー
https://www.wantedly.com/companies/company_6473754/post_articles/443064
▼東京オフィスの応募はこちら
https://www.wantedly.com/projects/1468324
▼新潟オフィスの応募はこちら
https://www.wantedly.com/projects/1468155