Unity の C# イベントではてな ? をつけ忘れるとデバッグしにくいエラーが出るので注意したいメモです。
この記事は 2022年 ゆるくすすめる ( ワンフットシーバス ) | GWアドベントカレンダー の 5/5 7日目の記事でもあります。
Unity の C# イベントではてな ? をつけ忘れてハマった
C# での書き方なので、あまり Unity のバージョンで変化はなさそうです Unity 2018.4.24.f1 で書いていました。
Unity の C# イベントではてな ? をつけ忘れると、ちょっと追いにくいエラーが出て困っていました。これを機会に調べてみます。
まずうまくいくイベントを発生するクラス
どこかの GameObject に配置したクラスです。ごくシンプルに Update 時にイベントを発生させています。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SampleEvent : MonoBehaviour { // イベントを発生するクラス public delegate void OnUpdateDelegate(string result); public event OnUpdateDelegate UpdateHandler; void Start() { } void Update() { UpdateHandler?.Invoke("OK"); } }
以下が、イベントを受け取るクラスです。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SampleReceiver : MonoBehaviour { // イベントを受け取るクラス SampleEvent sampleEvent; void Start() { sampleEvent = GameObject.Find("SampleEvent").GetComponent<SampleEvent>(); sampleEvent.UpdateHandler += UpdateHandler; } void Update() { } void UpdateHandler(string result) { Debug.LogFormat("UpdateHandler result={0}", result); } }
こちらを実行すると、
このようにイベントが動作します。
UpdateHandlerの横のはてな ? を付けないとどうなるか?
UpdateHandler?.Invoke("OK");
こちらのはてな ? を付けないとややこしいエラーが起きました。
はてなマーク(クエスチョンマーク)自体を検索で文献を探しにくかったのですが、ようやく、以下の文献でわかりました。
C#での「?(クエスチョンマーク)」の意味 | nabelog
まず、C#での「?(クエスチョンマーク)はnull条件演算子」といって、対象の変数につけると、その値がたとえ null でもエラーにならず通るようになるようです。
イベント作成時に、1つもイベントに登録がないまま呼び出してしまうと例外が発生してしまいます。
このため、呼び出す前にヌルチェックをしなければいけませんが、それを幾つかの方法でスマートに行う事が出来ます。
とあるように、イベントはどこからも指定されてないと例外が発生するので、この null 条件演算子をつけると、その値がたとえ null でもエラーにならず通ることで、イベント未使用時にもエラーがなく通るようそうです。
エラー発生時を振り返ってみる
たしかに、イベントって、必ずしも作ったときにイベントを他から登録しないようなケースって結構多いですよね。
私が「デバッグしにくいエラー」という印象になったのも、
- 何かしらのイベント連携をしそうなときに、前もって設計してイベントを作っていた
- そして、 Unity を動かすと、まだどこからもイベント登録していないのにエラーが発生していた
- イベントでエラーが発生するときは、よく入れる値が型でおかしかったり null なケースが多いのに、その手前でエラーが起きているようにエラーメッセージから読み取れる
といった発生状況だったので、がしがし開発している佳境時に起きやすく、しかも「はてな ? をつけ忘れた!」と気づくまでで続けるというツラい状況でした。
ということで、あえてエラーを出してみる
では、あえて
UpdateHandler.Invoke("OK");
として、どうなるかやってみましょう。まず、イベントを発生するクラス。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SampleEvent : MonoBehaviour { // イベントを発生するクラス public delegate void OnUpdateDelegate(string result); public event OnUpdateDelegate UpdateHandler; void Start() { } void Update() { UpdateHandler.Invoke("OK"); // はてな、つけ忘れ } }
つづいて、イベントを受け取るクラスでは UpdateHandler の登録を外しておきます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SampleReceiver : MonoBehaviour { // イベントを受け取るクラス SampleEvent sampleEvent; void Start() { sampleEvent = GameObject.Find("SampleEvent").GetComponent<SampleEvent>(); // sampleEvent.UpdateHandler += UpdateHandler; // UpdateHandler の登録を外す } void Update() { } void UpdateHandler(string result) { Debug.LogFormat("UpdateHandler result={0}", result); } }
これで実行してみると、
無事?、エラーが出ました!
NullReferenceException: Object reference not set to an instance of an object
SampleEvent.Update () (at Assets/SampleEvent.cs:17)
いやー、改めて発生させても分かりにくいですね UpdateHandler.Invoke("OK")
だけをみると、入れる OK のデータも記述も合ってそうに見えるので「なぜエラーなんだ!」ってなりますね。ここで、はてなのつけ忘れに気づけば回避できるが、気づかないとずっと直らないやつ。
しかも、今回気づきましたが、イベントの登録が1つでも成立していると、このままで null は発生しないわけで、はてなをつけ忘れても例外は発生しません。この場合、はてなのつけ忘れは気づかないまま残る可能性があります。うわー。
ということで、イベント作成時に、この null 条件演算子の使い方を理解した上で、はてなをつける習慣をつけるというのを徹底していくというのが大切そうです。