C#で時々使われるDelegateは、調べてみると委譲とかラムダ式とかコールバックとか、更に難しい単語が出てきて混乱しますね。そこで、ここではC#のDelegateについて簡潔に解説してみたいと思います!
Delegateとは
Delegateは一言でいうと「メソッドを代入できる型」です。int型の変数には整数を代入できるし、string型には文字列を代入できますね。これと同じでDelegateで作った型にはメソッドを代入できます。
メソッドを代入できる型があって何が嬉しいかというと、コールバック(Callback)という仕組みがこれで実現できるようになるのです。
コールバックとは
例えばTimerクラスと、それを呼び出すSystemクラスがあり、Timerのカウント0になったらSystemクラスにそれを伝えたい場合を考えます。このカウントが終わったことを伝える矢印が、コールバックと呼ばれる仕組みになります。
これを実現するには、予めTimerクラスのdelegateにカウントダウン後に呼び出したいメソッドを設定しておきます。カウントが0になったときにdelegateを通してこのメソッドを実行することで、コールバックが実現できるというわけです。
理屈だけではわかりにくいので、実際にプログラムを書いて確かめてみましょう。
タイマーを作ってみる
Timerクラスを作る
では実際にDelegateを使ってコールバックの仕組みを作ってみましょう。まずはTimerクラスは次のようになります。
public class Timer { public delegate void OnCompleteDelegate(); public OnCompleteDelegate onComplete; public void CountDown() { // 本当はちゃんと3秒かけてカウントする Debug.Log(3); Debug.Log(2); Debug.Log(1); // コールバック onComplete(); } }
ここではTimerクラスの中でdelegateを使って「OnCompleteDelegate」という型を作っています。 デリゲート型の定義はdelegate キーワードを用いて次のように記述します。
delegate 戻り値の型 デリゲートの型名(引数);
イメージ的にはClassやStructで型を作るのと同じですね。次の行では、いま作ったOnCompleteDelegate型を使ってonComplete変数を作成しています。CountDownメソッドの中では、カウントが0になったタイミングでonCompleteに登録したメソッドを呼び出しています。
呼び出し元のクラスを作る
次に呼び出し元のクラスを作ります。
public class System : MonoBehaviour { void Start() { Timer timer = new Timer(); timer.onComplete += Alarm; timer.CountDown(); } void Alarm() { Debug.Log("カウントダウン終了"); } }
ここではタイマーのインスタンスを作成し、デリゲート(onComplete)にAlarmメソッドを登録しています。その後CountDownメソッドを呼び出すことで、カウントダウンが開始され、最後にonCompleteデリゲートに登録したAlarmメソッドが呼び出されます。
実行してみる
これを実行すると、コンソールには次のように表示されます。
UnityEventを使って作り直す
Delegateを使うとコールバックの仕組みが作れることが分かりました。ただ、Delegateで型を宣言してから、変数を作るところが少しメンドウですね・・・。そこでUnityにはUnityEventという仕組みが用意されていて、コールバックを簡単に記述できるようになっています。
UnityEventを使って書き換えてみましょう。まずTimerクラスは次のようになります。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; // 必要! public class Timer { public UnityEvent onComplete = new UnityEvent(); public IEnumerator CountDown() { // 本当はちゃんと3秒かけてカウントする Debug.Log(3); Debug.Log(2); Debug.Log(1); // コールバック onComplete.Invoke(); } }
まずUnityEventクラスを使うため、「using UnityEngine.Events;」を追加しています。またdelegateの代わりにUnityEventクラスの変数を作成しています。カウントダウン後はonComplete変数に登録したメソッドを呼び出すため、UnityEngineのInvokeメソッドを実行しています。
また、Timerを呼び出す側のSystemクラスは次のようになります。
public class System : MonoBehaviour { void Start() { Timer timer = new Timer(); timer.onComplete.AddListener(Alarm); timer.CountDown(); } void Alarm() { Debug.Log("カウントダウン終了"); } }
delegatreを使ったときはonCompleteにメソッドを直接代入していましたが、UnityEventを使う場合はAddListenerメソッドを使ってコールバックに使うメソッドを指定します。
実行してみて、カウントダウン後に「カウントダウン終了」と表示されるかを確かめてみてください!