イベントってややこしいですよね。
イベントやデリゲートで使う構文には、delegate/event/Action/Func(さらにはUnityAction/UnityFunc/UnityEvent)などがあり、いつも混乱しています。
さらに、メソッドの指定にラムダ式を使うことも多く混乱しやすいので順に理解していこうと思います。
まずはdelegateは「関数を入れることができる変数(の型)」と言えます。
代表的な使い方に「コールバック処理」があります。例えば長い時間がかかるメソッドの実行後、その結果を使って行いたい処理(コールバック)を、delegateに代入して渡して、処理の終了時に実行してもらう、という流れです
下記のサンプルは、Webサイトからhtmlを取得して、取得した結果をテキストにコールバックで表示しています。
テキストを更新する処理そのものは呼び出し側のDemoViewに定義されていますが、その実行を指示するのは呼び出されたDemoApplication側の処理の最後になります。
//呼び出し側のクラス。シーンのオブジェクトにアタッチして使用する。 public class DemoView : MonoBehaviour { [SerializeField] TextMesh textMesh; void Start() { DemoApplication demoApp = new DemoApplication(); demoApp.CompleteHandler = UpdateTextCallback; //コールバックしてもらうメソッドを代入 demoApp.GetHtmlAsync(); } void UpdateTextCallback(string html) { textMesh.text = html; } } //DemoViewから呼び出されるクラス public class DemoApplication { public delegate void OnCompleteDelegate(string html); public OnCompleteDelegate CompleteHandler; public async void GetHtmlAsync() { HttpClient client = new HttpClient(); var result = await client.GetStringAsync("https://torikasyu.com/"); if(CompleteHandler != null) CompleteHandler(result); //代入されたメソッドが存在すれば実行する //CompleteHandler?.Invoke(result); //別の書き方(おすすめ) } }
eventとは、上記で使ったdelegate専用の修飾語になります。eventを使うと、下記に書き換わります。
public class DemoView : MonoBehaviour { [SerializeField] TextMesh textMesh; void Start() { DemoApplication demoApp = new DemoApplication(); //demoApp.CompleteHandler = UpdateTextCallback; //丸ごと置き換える処理は禁止されて.. demoApp.CompleteHandler += UpdateTextCallback; //メソッドの「追加」しかできなくなる demoApp.CompleteHandler += ((html)=>Debug.Log(html)); //更に別のメソッドも追加できる(これがラムダ式だ!) demoApp.GetHtmlAsync(); } void UpdateTextCallback(string html) { textMesh.text = html; } } //DemoViewから呼び出されるクラス public class DemoApplication { public delegate void OnCompleteDelegate(string html); public event OnCompleteDelegate CompleteHandler; //event修飾語をつけた public async void GetHtmlAsync() { HttpClient client = new HttpClient(); var result = await client.GetStringAsync("https://torikasyu.com/"); CompleteHandler?.Invoke(result); } }
それほど大きな違いは無く見えますが、delegateに対する外部クラスからの操作が制限され、より安全に実行できるようになっています。
ということで便利なdelegateですが、ぶっちゃけ2行も書くのが面倒くさいです。C#を作った人もそう思ったのか、もっと簡単に書く方法があります。
(using System;が必要です)
//呼び出し側のクラス。シーンのオブジェクトにアタッチして使用する。 public class DemoView : MonoBehaviour { [SerializeField] TextMesh textMesh; void Start() { DemoApplication demoApp = new DemoApplication(); demoApp.CompleteHandler += UpdateTextCallback; //メソッドの「追加」しかできなくなる demoApp.CompleteHandler += ((html)=>Debug.Log(html)); //更に別のメソッドも追加できる demoApp.GetHtmlAsync(); } void UpdateTextCallback(string html) { textMesh.text = html; } } //DemoViewから呼び出されるクラス public class DemoApplication { //public delegate void OnCompleteDelegate(string html); //public event OnCompleteDelegate CompleteHandler; public event Action<string> CompleteHandler; //上の2行を書き換えた public async void GetHtmlAsync() { HttpClient client = new HttpClient(); var result = await client.GetStringAsync("https://torikasyu.com/"); CompleteHandler?.Invoke(result); } }
呼び出される側のクラスの2行が1行になりました。なのでActionは、この場面では「簡単に書けるdelegate」と思って問題ないかと思います(ある場合はTwitterでご指摘くださいw)
上記の例で代入したメソッドはvoidで返り値が無いのでActionを使いましたが、戻り値がある場合はFuncを使い、Invoke時に取得することができます。(あまりコールバックでFuncを使用することは無いのでサンプルは省略します)
ではUnity独自のUnityAction/UnityFuncなのですが、役割としてはAction/Funcと全く同じです(たぶん)。好きな方を使ってください。
似ているようで異なるのがUnityEventです。上記で出てきたeventの機能拡張版といえばそうなのですが、delegateを修飾するものではなく、単体で利用できます。
UnityEventをpublicの変数として定義すると、Editorのインスペクタから自由に実行したい処理を登録できるようになります。Editor上で処理を作っていくときには便利ですね。
public class DemoView : MonoBehaviour { [SerializeField] TextMesh textMesh; public UnityEvent OnCompleteAction; //インスペクタから登録できる void Start() { DemoApplication demoApp = new DemoApplication(); demoApp.CompleteHandler += UpdateTextCallback; demoApp.GetHtmlAsync(); } void UpdateTextCallback(string html) { textMesh.text = html; OnCompleteAction?.Invoke(); //インスペクタで登録した処理が実行される } }
To Be Written…