【Unity/C#】コルーチンを自前で動かすためにIEnumeratorについて簡単に調べた

# コルーチンを自前で動かしたい
コルーチンは`MonoBehaviour`を継承していれば`StartCoroutine`で動かすことができます。しかし、`MonoBehaviour`を継承していなくても使いたい場面は割と多いです、多分。
`MonoBehaviour`なしでコルーチンを動かす方法は調べるとそれなりに出てきますが、そもそもコルーチンで使われている`IEnumerator`とはなんぞ？から始めたい所存です。

# IEnumeratorインターフェース？
[MSDN][1]を見てみるとシンプルに書かれています。
>非ジェネリック コレクションに対する単純な反復処理をサポートします。

`IEnumerator`インターフェースは反復処理を実現するためのインターフェースになります。
コルーチンはUnityが反復処理をいい感じに実行してくれてる仕組みというわけですね。

# 反復処理を作って動かしてみる
とりあえず物は試しということでUnityで適当な反復処理を作ってみます。
わかりやすくデバッグログも出してこんな感じに。

```cs:SampleCoroutine.cs
public IEnumerator Coroutine() {
	Debug.Log("Coroutine 1");
	yield return 1;

	Debug.Log("Coroutine 2");
	yield return 2;
}
```

こいつをこんな感じで動かしてみます

```cs:SampleCoroutine.cs
public void Sample() {
	var iEnumerator = Coroutine();
	while (iEnumerator.MoveNext()) {
		Debug.Log($"Coroutine Return {iEnumerator.Current}");
	}
}
```

```txt:Console
Coroutine 1
Coroutine Return 1
Coroutine 2
Coroutine Return 2
```

おもったよりかんたん！

## 何をやっているの？
まずは反復処理についてです。何気なく使っている`yield`ですが、`yield`を含むブロックのことをイテレータブロックと言います。
大雑把に説明すると「1回の処理でここまでやってね」というブロックになります。`yield`には値を返す`return`と処理を抜ける`break`の2つのキーワードがあります。

次に`MoveNext`です。これはイテレータを次に進ませます。イテレータが進んだ場合は`true`を返すため、`while`を使うとイテレータを全て進めることができます。

`Current`は反復処理が返した値を保持しているプロパティです。`yield return`で返した値がそのまま入ってくる感じですね。
`object`型なので色んなものを返せます。

```cs:SampleCoroutine.cs
public IEnumerator Coroutine() {
	yield return 1;
	yield return 9.6;
	yield return "ほげえ";
}
```

```txt:Console
Coroutine Return 1
Coroutine Return 9.6
Coroutine Return ほげえ
```

また、`Reset`に関してですが、こちらは自前で実装する必要があります（実装してないと`NotSupportedException`がスローされます）。
単純に`IEnumerator`インターフェースで実装した反復処理メソッドをやり直したい場合は対象のメソッドを再び取得して動かします。

```cs:SampleCoroutine.cs
public void Sample() {
	var iEnumerator = Coroutine();
	while (iEnumerator.MoveNext()) {
		Debug.Log($"Coroutine Return {iEnumerator.Current}");
	}

	// iEnumerator.Reset();		<- NotSupportedExceptionがスローされる
	iEnumerator = Coroutine();
	while (iEnumerator.MoveNext()) {
		Debug.Log($"Coroutine Return {iEnumerator.Current}");
	}
}
```

思ったよりシンプル！これなら`StartCoroutine`なしでもなんとかなりそう・・・？

## WaitFor関係は更に自前で実装しないとダメ
注意点として`WaitForSecounds`等は単純に`yield return`しただけではうまくいかないです。
よくあるのが`yield return WaitForSeconds`でカウントをするコルーチンですが、単純に`MoveNext`をしただけではできません。
`MoveNext`は1フレーム毎にイテレータを進めるというイメージが良いと思います。この辺は`StartCoroutine`がいい感じに処理してくれてる部分です。

## 反復処理の入れ子をやりたい
反復処理から反復処理を実行するような形にすればできます。実は`IEnumerator`インターフェースを調べ始めたきっかけでもあります。

```cs:SampleCoroutine.cs
public IEnumerator Coroutine() {
	Debug.Log("Coroutine Start");

	var coroutine2 = Coroutine2();
	while (coroutine2.MoveNext()) {
		yield return coroutine2.Current;
	}
	Debug.Log("Coroutine End");
}

public IEnumerator Coroutine2() {
	Debug.Log("Coroutine2 1");
	yield return 1;

	Debug.Log("Coroutine2 2");
	yield return 2;
}

public void Sample() {
	var iEnumerator = Coroutine();
	while (iEnumerator.MoveNext()) {
		Debug.Log($"Coroutine Return {iEnumerator.Current}");
	}
}
```

```txt:Console
Coroutine Start
Coroutine2 1
Coroutine Return 1
Coroutine2 2
Coroutine Return 2
Coroutine End
```

# おしまい
何気なく使っていたコルーチンと`IEnumerator`インターフェースですが、思ったよりシンプルでした。
これでもまだ触りの部分だと思いますが、当初の目的を達成しつつある程度は分かった気がしたのでとりあえずOKとしましょう。

何かあったらコメントください。

# 参考
[++C++; // 未確認飛行 C イテレーター][2]
[WisdomSoft 処理の分割（コルーチン）][3]
[Qiita:[C#]IEnumeratorとIEnumerableを調べた][4]

[1]:https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.ienumerator?view=netframework-4.8]
[2]:https://ufcpp.net/study/csharp/sp2_iterator.html
[3]:http://www.wisdomsoft.jp/656.html
[4]:https://qiita.com/vc_kusuha/items/2048391d821cb94fa489