Tips1でforeachで反復できる実装の仕方を紹介しました。
要は、foreach(★ in ●)に対して、●を返すためにSystem.Collections.IEnumerableインターフェースを実装して、GetEnumerator()によって取得できるようにしました。また、★にデータを入れるためにSystem.Collections.IEnumeratorインターフェースを実装しました。
ところでC#にも最近流行りの反復子と呼ばれる機構が備わっています。
次の例では
10
20
30
と表示されます。
反復子を実装するメソッドには戻り値としてSystem.Collections.IEnumerableインターフェース型を返します。
反復子はyield returnで、yield単体では意味を持ちません。従ってyieldという変数名は使用可能です。(しない方がいいです)
yield returnがあらわれると、値を返して、メソッド内の処理を一時中断します。
そして再びforeachによって内部でGetEnumerator()が呼ばれると前の処理から再開して次のyield returnまで実行されます。
class Program {
public static void Main() {
foreach(int x in getValues()) {
System.Console.WriteLine(x);
}
}
static System.Collections.IEnumerable getValues() {
yield return 10;
yield return 20;
yield return 30;
}
}
ところが、例えば次のようにしてもコンパイルエラーになりません。
class Program {
public static void Main() {
foreach(string x in getValues()) {
System.Console.WriteLine(x);
}
}
static System.Collections.IEnumerable getValues() {
yield return 10;
yield return 20;
yield return 30;
}
}
string型で受け取れるはずがないので、例外が発生します。
コンパイル時に型チェックを有効にするために、System.Collections.Generic.IEnumerable<int>を実装します。
こうすることで次の例ではしっかりエラーになります。
class Program {
public static void Main() {
foreach(string x in getValues()) {
System.Console.WriteLine(x);
}
}
static System.Collections.Generic.IEnumerable<int> getValues() {
yield return 10;
yield return 20;
yield return 30;
}
}
yield returnはもちろんfor文などの中で使用可能です。むしろそのためにあります。
クラスを使う人のために、極力クラスをfor文で回したりしないように実装します。
例えば本クラスで、本の全てを列挙するためにlistup()を実装しました。
更に逆順に列挙するためにlistupReverse()を実装しました。
利用者は、内部のデータ構造を知らなくても昇順・逆順で列挙することができるのです。
struct Book {
public string title;
}
class Booklist {
Book[] books;
public Booklist() {
books = new Book[10];
for(int i = 0; i < 10; i++) {
books[i].title = ((char)('a' + i)).ToString();
}
}
public System.Collections.Generic.IEnumerable<Book> listup() {
for(int i = 0; i < 10; i++) {
yield return books[i];
}
}
public System.Collections.Generic.IEnumerable<Book> listupReverse() {
for(int i = 9; i >= 0; i--) {
yield return books[i];
}
}
}
class Program {
public static void Main() {
Booklist bl = new Booklist();
foreach(Book book in bl.listup()) {
System.Console.WriteLine(book.title);
}
foreach(Book book in bl.listupReverse()) {
System.Console.WriteLine(book.title);
}
}
}
結局、yieldを使うと前に紹介したIEnumeratorを実装したクラスが生成され、反復されるだけです。
というわけで要素数100000000でベンチマークをしてみました。
前回のIEnumerator実装 00:00:02.5961485
今回のyield return実装 00:00:02.3300033
余り変わらないですね。今回のソースコードは以下の通りです。
class MyClass {
int[] ary;
const int MAX_SIZE = 100000000;
public MyClass() {
ary = new int[MAX_SIZE];
}
public void setSeriesData() {
for(int i = 0; i < MAX_SIZE; i++) {
ary[i] = i;
}
}
public System.Collections.Generic.IEnumerable<int> GetEnumerator() {
for(int i = 0; i < MAX_SIZE; i++) {
yield return ary[i];
}
}
}
class Program {
public static void Main() {
MyClass mc = new MyClass();
long sum = 0;
mc.setSeriesData();
System.DateTime dt = System.DateTime.Now;
foreach(int x in mc.GetEnumerator()) {
sum += x; // 反復
}
System.Console.WriteLine(System.DateTime.Now - dt);
}
}
0 件のコメント:
コメントを投稿