連載:[完全版]究極のC#プログラミング

Chapter3 新しい繰り返しのスタイル ― yield return文とForEachメソッド

川俣 晶
2009/08/31

3.4 C# 3.0によるRangeクラスの実装

 この2つのうっとうしさは、C# 3.0の反復子の機能を使って書き直すと、きれいさっぱり消えてなくなる

 ともかく、圧倒的にスリムになったコードを見ていただきたい(リスト3.2参照)。

using System;
using System.Collections.Generic;

class Range
{
  private int from, to;

  public IEnumerator<int> GetEnumerator()
  {
    for (int i = from; i <= to; i++) yield return i;
  }

  public Range(int from, int to)
  {
    this.from = from;
    this.to = to;
  }
}

class Program
{
  static void Main(string[] args)
  {
    foreach (int i in new Range(0, 9))
    {
      Console.Write("{0} ", i);
    }
    // 出力:0 1 2 3 4 5 6 7 8 9
  }
}
リスト3.2 C# 3.0によるRangeクラスの実装

 このように、2つ必要だったクラスは1つになり、MoveNextメソッド、Currentプロパティは消滅し、状態を記憶しておくフィールドは単なるローカルなループ変数へと立場を大幅に後退させてしまった。

 この程度のコード量であれば、気軽に列挙可能なオブジェクトを作ろう……という気にもなるだろう。

 さて、どのような構文がこの劇的な変化をもたらしたのだろうか?

 C# 3.0版で目新しい構文は、「IEnumerator<int>」と「yield return i;」しかない。このうち、前者はC# 1.xからあるIEnumerableインターフェース(System.Collections名前空間)のジェネリック版にすぎないので、型が明示されたことを除けばなんら目新しいことはない。

 つまり、注目ポイントは「yield return i;」だけである。ところが、このたった1つの文がすべてを劇的に変えてしまうのである。

 実は、「yield return文」と、後で紹介する「yield break文」を含むブロックは、通常のブロックではなく「反復子ブロック」というものになる。

 つまり、リスト3.2内の次のブロックは、反復子ブロックになるのである。

{
  for (int i = from; i <= to; i++) yield return i;
}

 反復子ブロックは通常のブロックとは異なり、すぐには実行されず、その代わりに列挙子オブジェクトというものが生成される。

 ソースコード上に書かなくてよくなったクラス(リスト3.1のRangeEnumクラスのようなクラス)に相当するクラスが自動的に用意され、そのインスタンスが作成されるのである。その作業はC#プログラマーからは見えない水面下の部分で自動的に行われる。

 しかし、そのような詳細を知る必要はない。foreach文で使うとすれば、C#プログラマーが知る必要があるのは、次のことだけである。

  • 反復子ブロックはすぐには実行されない
  • 実行されるのはforeach文で列挙が開始された後である
  • 反復子ブロック内でyield return文が実行されると、その引数の値がforeach文に渡り、文が1回実行される
  • foreach文に指定された実行文(リスト3.2ではConsole.Writeメソッド)の実行が終わると、反復子ブロック内の続きが実行される

 ここで注意が必要なのは、yield return文は、return文と同じように記述し、同じように値を呼び出し側に返すが、呼び出し側の処理が一段落するとその続きが再度実行されるという点である。

 たとえば、次ページのリスト3.3には2つのyield return文が存在するが、これらは双方とも実行される。最初のyield return文で終わってしまうわけではない。一時的に呼び出し元に実行権を譲るだけである。

using System;
using System.Collections.Generic;

class Sample
{
  public IEnumerator<char> GetEnumerator()
  {
    Console.Write("[Cを返す]");
    yield return 'C';

    Console.Write("[#を返す]");
    yield return '#';
  }
}

class Program
{
  static void Main(string[] args)
  {
    foreach (char c in new Sample())
    {
      Console.Write("{0}", c);
    }
    // 出力:[Cを返す]C[#を返す]#
  }
}
リスト3.3 実行継続されるyield return文の確認

 実行結果を見るとわかるとおり、foreach文で指定された実行文と反復子ブロック内の文は交互に実行される。このような実行形態はあまり例がなく、一見わかりにくいかもしれない。しかし、それによってソースが短くなり、コーディングの負担も減り、楽ができるわけである。楽ができる以上は、正しく理解するために努力する価値がある。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter3 新しい繰り返しのスタイル ― yield return文とForEachメソッド
    1.3.1 「繰り返し」という古くて新しい問題
    2.3.2 数を数えるというサンプル
    3.3.3 C# 1.xによるRangeクラスの実装
  4.3.4 C# 3.0によるRangeクラスの実装
    5.3.5 yield break文による中断
    6.3.6 yieldは予約語ではない
    7.3.7 1つのクラスに複数の列挙機能を付ける
    8.3.8 自動的に作られるオブジェクトと二重利用
    9.3.9 catchできない制約
    10.3.10 制約の真相―見た目と違う真実の姿
    11.3.11 ForEachメソッドを使う別解
    12.3.12 性能比較
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間