今回の記事ではコレクションに関係するIEnumearble
とIEnumerator
について解説していきます。
IEnumerable
およびIEnumerator
はコレクションの要素を取り出す操作を一般化したものになります。少し理解しづらいかもしれませんが、便利なものなので是非マスターしてください。
コレクションの関連記事:
また、UnityではコルーチンというIEnumerator
を返すメソッドがあります。こちらも便利なUnityの機能になりますので使えるようになると便利です。
foreach文に関係するIEnumerable・IEnumeratorとは
IEnumerable
とIEnumerator
はどちらもコレクションの要素を一つづつ参照していくためのインターフェースになります。
コレクションの要素を見る時はループ処理を利用しますが、実はこれらのインターフェースが提供しているメンバだけで十分にコレクションの要素をループさせることができます。
初めて使う方から見るとわかりづらいかもしれませんが、先人達の経験に習う感じで使ってみてください。
- IEnumerator:コレクションから取得できる。取得したコレクションの要素を一つづつ参照する操作をまとめたインターフェース。
- IEnumerable:
IEnumerator
を作成するためのインターフェース。Linq操作に対応しており、直接IEnumerator
を使用するよりこちらを使用した方が便利。
これらインターフェースにはテンプレート機能を利用したものとそうではないものが存在しています。各々の名前空間は次のものになります。
System.Collections
:テンプレートなしの方System.Collections.Generic
:テンプレート有りの方
テンプレートなしの方では値はobject型として扱われますので、使用する時は型変換を行ってください。
C#ではコレクション型はIEnumerableインターフェースを継承していますので、IEnumerable.GetEnumeratorメソッドでコレクションに対応するIEnumeratorを取得できます。
foreach文を使わずにループさせてみよう!
それではforeach文を使わずにループさせてみましょう!
現在では通常は必要になる機会はないですが、昔のコードなどforeach文がまだない時に作られたクラスではここのサンプルコードのようなやり方でループさせる必要があったんです。
Unityのクラスでもたまにそんなクラスがあるので豆知識として覚えておくと便利です。
foreach文を使わずにループする時はIEnumeratorを直接使用します。
IEnumerableの場合はGetEnumeratorメソッドを使用してIEnumeratorを取得してください。
IEnumeratorには次のメンバがあり、これらのメンバだけでコレクションの要素をループさせられます。
- Currentプロパティ:現在の要素
- MoveNextメソッド:次の要素に移動する。全ての要素に移動後、移動できなかった場合はfalseを戻り値として返します。
- Resetメソッド:IEnumeratorを最初の状態に戻す。
1 2 3 4 5 6 7 8 |
var list = new List<int>() { 0, 1, 2, 3, 4 }; var enumerable = list.GetEnumrable(); var e = enumerable.GetEnumerator(); while(e.MoveNext()) { var element = (int)e.Current; // elementに対する処理を書く } |
IEnumerable・IEnumeratorを作ってみよう!
IEnumerable
・IEnumeartor
はインターフェースなのでそれらを継承するクラスを定義することもできます。
IEnumeratorを定義する書き方
次のサンプルコードではリスト型をコンストラクタから受け取り、そのリストの要素を全て見ていくIEnumerable
とIEnumerator
を定義しています。
これらを使う時は
using System.Collections
を宣言するのを忘れないようにしましょう。
リストにはこれと同じものを既に定義しているのであまり意味はないクラスになりますが、書き方の参考としてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//テンプレート付きとなしの両方を継承した方が便利。 public class MyEnumerable : IEnumerable<int>, IEnumerable { List<int> _list; public MyEnumerable(List<int> list) { _list = list; } //直接IEnumeratorを返すとIEnumeratorの定義を省略することができる。 public IEnumerator<int> GetEnumerator() { return new Enumerator(_list); } //IEnumerableのメンバはテンプレート付きの方を再利用すると楽。 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); //Enumeratorもテンプレート有無両方を継承した方が便利 public class Enumerator : IEnumerator<int>, IEnumerator { List<int> _list; int _index = -1; //初めにMoveNextメソッドを呼び出し添字を+1するので0だと先頭要素を飛ばしてしまう。 public Enumerator(List<int> list) { _list = list; } public int Current { get => _list[_index]; } object IEnumerator.Current { get => _list[_index]; } //次の要素に移動する public bool MoveNext() { //一つ進めて、添字がリストの範囲内か確認している。 _index++; return _index < _list.Count; } //添字をリセットする。 public void Reset() { _index = -1; } } } |
IEnumeratorを定義しない書き方
また毎回IEnumeratorを継承して定義するのは大変なので、yieldキーワードを使用し次のように省略することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//テンプレート付きとなしの両方を継承した方が便利。 public class MyEnumerable : IEnumerable<int>, IEnumerable { List<int> _list; public MyEnumerable(List<int> list) { _list = list; } //直接IEnumeratorを返すとIEnumeratorの定義を省略することができる。 public IEnumerator<int> GetEnumerator() { //偶数の値だけループさせるようにしている。 foreach(var e in _list) { if((e % 2) == 0) yield return e; } } //IEnumerableのメンバはテンプレート付きの方を再利用すると楽。 public IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } |
こちらのサンプルではIEnumerable.GetEnumeratorメソッド内でIEnumerator型を直接返すのではなくyieldキーワードを使用して戻り値を返しています。
IEnumeratorを戻り値に持つメソッドは上のように書くこともでき、途中でメソッドを中断できます。
yield return
と書くとメソッドはその場所で中断します。処理を再開したい時はIEnumerator.MoveNextメソッドを呼び出してください。
yield
キーワードの詳しい書き方については次のコルーチンを見てください。
コルーチンについて
Unityではコルーチン(Coroutine)というメソッドの処理を一時中断させる機能があります。戻り値にIEnumeratorを返すメソッドをコルーチンとして使用できます。
コルーチンの定義の仕方
IEnumeratorを戻り値に持つメソッドの場合は戻り値の前に必ずyieldキーワードを先に付けてください。
- yieldキーワードの書き方:
yield return <戻り値>
また、メソッドの途中で処理を終了させたい場合はbreakキーワードを次のように書いてください。
- メソッドを終了させたい時:
yield break;
Unityではコルーチンが返す戻り値として使えるクラスがいくつか用意されています。それらのクラスを使用することで、コルーチンが中断した際にどのタイミングで処理を再開するかを指定することができます。
以下のクラスは全てUnityEngine.YieldInstructionクラスから派生しています。
-
WaitForEndOfFrame
-
WaitForFixedUpdate
-
WaitForSeconds
-
WaitForSecondsRealtime
-
WaitUntil
-
WaitWhile
また、nullを返すと次のフレームまで処理の実行を中断することができます。
コルーチンの使い方
コルーチンを定義しましたら、MonoBehaviour.StartCoroutine
メソッドで実行しましょう!
StartCoroutine
メソッドは引数にIEnumerator
を受け取り、問題なければUnityEngine.Coroutine
型を戻り値として返します。
- コルーチンの実行:Coroutine c = MonoBehaviour.StartCoroutine(<コルーチンの戻り値を渡す>);
このCoroutine型は実行したコルーチンを表すものになり、次のメソッドで使用されます。
MonoBehaviour.StopCoroutine
メソッド:実行中のコルーチンを終了させる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using UnityEngine; using System.Collections; public class Sample : MonoBehaviour { Coroutine _coroutine; void Start() { _coroutine = StartCoroutine(Test(10)); } IEnumerator Test(float a) { yield return new WaitForSeconds(a); //a秒待ってから処理を実行する感じ } } |
IEnumeratorを返すStartメソッドについて
MonoBehaviour.Start
メソッドの戻り値にはvoid
ではなくIEnumerator
を指定することができます。
このように書くとStartメソッド自体がコルーチンとして実行されますので、豆知識として覚えておくと便利です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using UnityEngine; using System.Collections; public class Sample : MonoBehaviour { IEnumerator Start() { //前処理 yield return new WaitForSeconds(1f); //1秒待ってからなにか処理をする yield return new WaitForSeconds(2f); //2秒待ってから何か処理をする //... } } |
【実践】コルーチンを作ってみよう!
それでは実際にコルーチンを使ってみましょう!
本サイトではこちらの記事でもコルーチンを使用しているので参考にしてみてください。
ここでのサンプルコードではGameObjectを一定間隔でワープさせるものになります。
一定間隔待つのにWaitForSecondsクラスを利用しています。コルーチン内でそのクラスをyieldキーワードを使用して戻り値として返しているのに注目してください。
また、GameObjectをクリックしたらワープを停止するようにしています。OnMouseUpAsButtonメソッドの中でStopCoroutineを呼び出している部分がコルーチンを停止させている処理になります。
一応、nullを渡すと例外が発生するのでnullチェックを行っています。また、コルーチンの実行が終わった場合でもCoroutine側には何も影響を与えないため、終了タイミングでnullを設定している部分にも注目してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using UnityEngine; using System.Collections; public class Sample : MonoBehaviour { public float WarpLength = 2f; [Range(1, 10)] public float Interval = 1f; Coroutine _warpCoroutine; //指定した秒数でGameObjectをワープさせるコルーチン IEnumerator Warp() { while (true) { yield return new WaitForSeconds(Interval); transform.position += Vector3.right * WarpLength; } } void Start() { _warpCoroutine = StartCoroutine(Warp()); } //クリックしたらワープを止める void OnMouseUpAsButton() { if(_warpCoroutine != null) StopCoroutine(_warpCoroutine); _warpCoroutine = null; } } |
まとめ
今回の記事ではIEnumerable
・IEnumerator
とUnityのコルーチン
について解説してきました。簡単にまとめると以下のようになります。
- IEnumeratorはコレクションの要素を探索するときに使用する。
- IEnumerableはIEnumeratorを作るクラスのこと。
- IEnumeratorは直接使用するのではなくIEnumerableを経由して使用する方が便利。
- IEnumerableを利用するときはLinqという必要な要素だけ取り出す機能が利用できる。
- UnityのコルーチンではIEnumeratorを返すメソッドが使われる。
- IEnumeratorを返すメソッドにはyieldキーワードを付けた戻り値を返す必要がある。
それでは次の記事に行ってみましょう!
コメント