C#のイベントとR3で綺麗なコードを書きたい

農工大アドカレ6日目、当日生成記事でございます！

内容は薄め、C#初心者向けだよ！

## イベントとは

C# でイベントはこんな感じのやつだよ！

```cs
using System;

public class Example
{
    public event Action OnEventTriggered;

    public void TriggerEvent()
    {
        OnEventTriggered?.Invoke();
    }
}
```

`OnEventTriggered` に処理を登録しておくと、`Invoke()` でイベント発火されたときに処理を実行できる！あとでもうちょっとちゃんと説明するよ!

## イベントを使うと何が嬉しいのか

**一言でいうと、コードが綺麗になるよ！**

### イベントを使わない場合

ゲームの中で状態を遷移させたいことがあるよね？例えば、待機状態からプレイ状態に変えたいとするよ。これに伴ってメニューのUIも変化するとしよう。こんな風にステートが定義されている。

```cs 
public enum GameState
{
  Waiting,
  Playing,
}
```

GameStateManager クラスからステートを変えられるようにしよう。

```cs
using System;

public class GameStateManager
{
    private MenuPresenter menuPresenter;

    public void ChangeGameState(GameState gameState)
    {
        switch (gameState)
        {
            case GameState.Waiting:
                break;
            case GameState.Playing:
                // プレイ状態になるときの処理
                menu.toPlaying();
                break;
        }
    }
}
```

一見問題なさそうだけど、`GameStateManager` が `MenuPresenter` のメソッドを直接呼び出しているよね？これはよくないよ！なぜなら、

- メニュー以外にも UI がある場合、`GameStateManager` はどんどん肥大化していく
- このままだと、`MenuPresenter` が変わったら `GameStateManager` も変えないといけない
- これは 「`GameStateManager` が `MenuPresenter`に依存している状態」だよ

### イベントを使う場合

まずは `GameStateManager` に `OnGameStateChanged` というイベントを追加するよ。ステートを変更するときはこのイベントを発火するだけ！

```cs
using System;

public class GameStateManager
{
    public event Action<GameState> OnGameStateChanged;

    public void ChangeGameState(GameState gameState)
    {
        OnGameStateChanged?.Invoke(gameState);
    }
}
```

`MenuPresenter` はこのイベントをサブスクライブするよ！上の `OnGameStateChanged.Invoke(gameState)` が呼ばれると、
`OnGameStateChanged` に登録されているメソッドが呼ばれるよ！`+=` で登録、`-=` で解除するよ！`-=` を忘れるとメモリリークする可能性があるから気をつけてね！

```cs
using System;

public class MenuPresenter: MonoBehaviour
{
    private GameStateManager gameStateManager; // DI などで注入すると良い(今回は省略)

    private void Start()
    {
        // シーン開始時にイベントに登録
        gameStateManager.OnGameStateChanged += OnGameStateChanged;
    }

    private void OnDestroy()
    {
        // シーン終了時にイベントから解除
        gameStateManager.OnGameStateChanged -= OnGameStateChanged;
    }

    public void toPlaying()
    {
        // プレイ状態になるときのUI処理。
    }

    private void OnGameStateChanged(GameState gameState)
    {
        switch (gameState)
        {
            case GameState.Waiting:
                break;
            case GameState.Playing:
                toPlaying();
                break;
        }
    }
}
```

これで、`GameStateManager` は `MenuPresenter` に依存していないよ！だから、

- `MenuPresenter` が変わっても、`GameStateManager` は変更しなくていい
- メニュー以外の UI があっても、`GameStateManager` は変更しなくていいし、肥大化しない


ちなみに、`IGameStateManager` というインターフェースを作ってあげると、`MenuPresenter` と `GameStateManager` の依存関係をさらに緩和できるよ！

```cs
using System;

public interface IGameStateManager
{
    event Action<GameState> OnGameStateChanged { get; }
}
```

## R3 を用いる

R3はC#でリアクティブプログラミングを行うためのライブラリだよ！R3を使うと、イベントを使ったコードをさらに綺麗に書くことができるよ！

https://github.com/Cysharp/R3

上記の例を R3 を使って書くとこんな風に書けるよ！

```cs
using System;
using R3;

public class MenuPresenter: MonoBehaviour
{
    private GameStateManager gameStateManager; // DI などで注入すると良い(今回は省略)

    private void Start()
    {
        Observable.FromEvent<GameState>(h => gameStateManager.OnGameStateChanged += h, h => gameStateManager.OnGameStateChanged -= h)
            .Where(gameState => gameState == GameState.Playing)
            .Subscribe(_ => toPlaying());
            .AddTo(this); // このコンポーネントが破棄されたら購読を解除する
    }

    public void toPlaying()
    {
        // プレイ状態になるときのUI処理。
    }
}
```

なんかスッキリしたね！`Where` でフィルタリング、`Subscribe` で購読、`AddTo` で購読解除を自動化しているよ！`OnDestroy` での購読解除を忘れる心配もなくなった！

R3は普通のイベントだけでなく、Unity の `Update` やインターバルなど色々なものをリアクティブに扱うことができるよ！ぜひ使ってみてね！

## まとめ

自信ないので何か間違ったことを言っていたら教えてください！