[Unity] UnityEventの動作を調べてみた

#概要
UnityEventはC#のeventよりもほんのちょっとだけ便利に使える。
https://docs.unity3d.com/ScriptReference/Events.UnityEvent.html

利用にあたってのエラーケース等を調べる。

#環境
Unity5.5.2p4

#結果
- nullは登録しちゃダメ:sweat: (登録はできるがInvoke時に実行時例外になる)
- リスナー登録時、重複チェックはしていない。
- 重複して登録しても1回のRemoveListenerで削除される。

- GetPersistentEventCount()はいつもゼロ。

#テスト内容
ボタンを用意して押したときにUnityEventに登録されたリスナーにイベントを送信する。

##ボタン処理の実装
UnityEngine.UI.Buttonにスクリプトをアタッチした。

```csharp:UnityEventInvoker.cs
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace My
{
    public class UnityEventInvoker : MonoBehaviour
    {
        [SerializeField]
        Button _button;

        public class OnPressedButton : UnityEvent<UnityEventInvoker> { };
        public OnPressedButton onPressedButton = new OnPressedButton();

        private void Awake()
        {
            EventTrigger eventTrigger = _button.gameObject.AddComponent<EventTrigger>();

            EventTrigger.Entry entry = new EventTrigger.Entry();
            entry.eventID = EventTriggerType.PointerDown;
            entry.callback.AddListener(OnPressedButtonTest);
            eventTrigger.triggers.Add(entry);
        }

        void OnPressedButtonTest(BaseEventData eventData)
        {
            if (!_button.interactable)
                return;

            Debug.Log("UnityEventInvoker.OnPressedButtonTest() onPressedButton.GetPersistentEventCount:" + onPressedButton.GetPersistentEventCount());
            onPressedButton.Invoke(this);
        }

        public void OnPressedButtonRemove()
        {
            onPressedButton.RemoveAllListeners();
        }
    }
}
```

##正常系のテスト
ボタン押下イベント受信時にログを出力するだけ。

```csharp:UnityEventReceive.cs
using UnityEngine;

namespace My
{
    public class UnityEventReceive : MonoBehaviour
    {
        [SerializeField]
        UnityEventInvoker _invoker;

        private void Start()
        {
            _invoker.onPressedButton.AddListener(OnPressedButton);
        }

        void OnPressedButton(UnityEventInvoker invoker)
        {
            Debug.Log("UnityEventReceive.OnPressedButton() " + name + ", invoker:" + invoker.onPressedButton.GetPersistentEventCount());
        }
    }
}
```

適当なGameObjectにアタッチした。


```bat:ログ出力結果
UnityEventInvoker.OnPressedButtonTest() onPressedButton.GetPersistentEventCount:0
UnityEventReceive.OnPressedButton() UnityEventReceive, invoker:0
```

GetPersistentEventCount()は登録されているリスナーの数を返すかと思ったけど0？

##登録したままでGameObjectを削除する
すでに削除済みだからエラーが出るのではないか？


```csharp:UnityEventKillSelf.cs
using UnityEngine;

namespace My
{
    public class UnityEventKillSelf : MonoBehaviour
    {
        [SerializeField]
        UnityEventInvoker _invoker;

        private void Start()
        {
            _invoker.onPressedButton.AddListener(OnPressedButton);
            Destroy(gameObject);
        }

        void OnPressedButton(UnityEventInvoker invoker)
        {
            Debug.Log("UnityEventKillSelf.OnPressedButton() " + name + ", invoker:" + invoker.onPressedButton.GetPersistentEventCount());
        }

        private void OnDestroy()
        {
            Debug.Log("UnityEventKillSelf.OnDestroy()");
        }
    }
}
```

```bat:ログ出力結果
UnityEventKillSelf.OnDestroy()
UnityEventInvoker.OnPressedButtonTest() onPressedButton.GetPersistentEventCount:0
UnityEventReceive.OnPressedButton() UnityEventReceive, invoker:0
```

特にリスナーを解除しなくてもイベントは通知できた。
そしてエラーはでなかった。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/36154/5f998df8-d64c-14d2-4c6f-4d03cd507f93.png)

スクリプトと同名のGameObjectを作ったがProfilerにはその存在が確認されない。
またヒエラルキーでもGameObjectが削除されていた。

ちなみにMonoBehaviourも存在しない。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/36154/f8a68440-ca9e-4079-3307-c3caf2f4ba12.png)

RemoveListenerの必要ない？


##リスナーとしてnullを登録する

```csharp:UnityEventNull.cs
using UnityEngine;

namespace My
{
    public class UnityEventNull : MonoBehaviour
    {
        [SerializeField]
        UnityEventInvoker _invoker;

        private void Start()
        {
            _invoker.onPressedButton.AddListener(null);
        }
    }
}
```

```bat:ログ出力結果
UnityEventInvoker.OnPressedButtonTest() onPressedButton.GetPersistentEventCount:0
NullReferenceException: Object reference not set to an instance of an object
  at UnityEngine.Events.BaseInvokableCall.AllowInvoke (System.Delegate delegate) [0x00002] in C:\buildslave\unity\build\Runtime\Export\UnityEvent.cs:117 
  at UnityEngine.Events.InvokableCall`1[My.UnityEventInvoker].Invoke (System.Object[] args) [0x00023] in C:\buildslave\unity\build\Runtime\Export\UnityEvent.cs:187 
  at UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) [0x00056] in C:\buildslave\unity\build\Runtime\Export\UnityEvent.cs:634 
  at UnityEngine.Events.UnityEventBase.Invoke (System.Object[] parameters) [0x0000e] in C:\buildslave\unity\build\Runtime\Export\UnityEvent.cs:769 
  at UnityEngine.Events.UnityEvent`1[T0].Invoke (.T0 arg0) [0x00016] in C:\buildslave\unity\build\Runtime\Export\UnityEvent_1.cs:53 
  at My.UnityEventInvoker.OnPressedButtonTest (UnityEngine.EventSystems.BaseEventData eventData) [0x0003c] in UnityEventInvoker.cs:32 
```

nullは登録できるが、Invokeするとエラーになる。
(それならnullチェックしてくれよ:sweat:)


##重複して登録する
同じメソッドをリスナーとして2回登録する。

```csharp:UnityEventDuplicate.cs
using UnityEngine;

namespace My
{
    public class UnityEventDuplicate : MonoBehaviour
    {
        [SerializeField]
        UnityEventInvoker _invoker;

        private void Start()
        {
            _invoker.onPressedButton.AddListener(OnPressedButton);
            _invoker.onPressedButton.AddListener(OnPressedButton);
        }

        void OnPressedButton(UnityEventInvoker invoker)
        {
            Debug.Log("UnityEventDuplicate.OnPressedButton() " + name + ", invoker:" + invoker.onPressedButton.GetPersistentEventCount());
        }
    }
}
```

```bat:ログ出力結果
UnityEventInvoker.OnPressedButtonTest() onPressedButton.GetPersistentEventCount:0
UnityEventDuplicate.OnPressedButton() UnityEventDuplicate, invoker:0
UnityEventDuplicate.OnPressedButton() UnityEventDuplicate, invoker:0
```

2回イベントが通知される。
重複チェックはしていない。


##重複して登録した後に削除する
同じものを2回登録する。
削除は1回のみ。

```csharp:UnityEventDuplicateRemove.cs
using UnityEngine;

namespace My
{
    public class UnityEventDuplicateRemove : MonoBehaviour
    {
        [SerializeField]
        UnityEventInvoker _invoker;

        private void Start()
        {
            _invoker.onPressedButton.AddListener(OnPressedButton);
            _invoker.onPressedButton.AddListener(OnPressedButton);
            _invoker.onPressedButton.RemoveListener(OnPressedButton);
        }

        void OnPressedButton(UnityEventInvoker invoker)
        {
            Debug.Log("UnityEventDuplicateRemove.OnPressedButton() " + name + ", invoker:" + invoker.onPressedButton.GetPersistentEventCount());
        }
    }
}
```

```bat:ログ出力結果
UnityEventInvoker.OnPressedButtonTest() onPressedButton.GetPersistentEventCount:0
```

重複して登録しても1回の削除処理ですべて削除される。


##Destroyするときにリスナーを解除する
OnDestroyのときにリスナーを解除する。

```csharp:UnityEventRemove.cs
using UnityEngine;

namespace My
{
    public class UnityEventRemove : MonoBehaviour
    {
        [SerializeField]
        UnityEventInvoker _invoker;

        private void Start()
        {
            _invoker.onPressedButton.AddListener(OnPressedButton);
            Destroy(gameObject);
        }

        void OnPressedButton(UnityEventInvoker invoker)
        {
            Debug.Log("UnityEventRemove.OnPressedButton() " + name + ", invoker:" + invoker.onPressedButton.GetPersistentEventCount());
        }

        private void OnDestroy()
        {
            Debug.Log("UnityEventRemove.OnDestroy()");
            _invoker.onPressedButton.RemoveListener(OnPressedButton);
        }
    }
}
```

```bat:ログ出力結果
UnityEventRemove.OnDestroy()
UnityEventInvoker.OnPressedButtonTest() onPressedButton.GetPersistentEventCount:0
UnityEventReceive.OnPressedButton() UnityEventReceive, invoker:0
```

削除しても他のリスナーには正常にイベントは通知できる。
当たり前な動作。


#感想
オープンソースじゃないのでその中身がどうやっているのか分からないのが困るなー。