連載:C# 2.0入門

第3回 新しい繰り返しのスタイル − yield return文とForEachメソッド

株式会社ピーデー 川俣 晶
2007/07/31
Page1 Page2 Page3 Page4

繰り返しという古くて新しい問題

 本稿を書くに当たってあらためて考えてみると、プログラミングにおいて「繰り返し」とは古くて新しい問題であることが痛感される。

 例えば、8bitワンボード・マイコンの全盛期、繰り返しはレジスタの値を1つずつ減らしてゼロになったら(ゼロ・フラグが立ったら)終わるという構造が多かった。ところが、最も人気のあった8080AやZ80といったCPUは、8bit単位でレジスタの値を1減らす命令(DCR/DEC)ではゼロ・フラグが変化したが、16bit単位でレジスタペアの値を1減らす命令(DCX/DEC)を実行してもゼロ・フラグが変化せず、ゼロ判定のために別途コードを書く面倒が必要だった。かといって、ライバルのモトローラ6800などでは、条件付きジャンプは8bit範囲の相対アドレスでしか飛べなかったので、ちょっとコードを書き足すとループの最初に戻れなくなってアセンブル・エラーということもあった。

 ともかく、繰り返しは頻出する割にいろいろな面倒をもたらしてくれた。しかも、常に進歩し続けているわけではなく、揺り戻しも起こる。

 例えば、コレクションの列挙といえば、Visual BasicならFor Each文を使って簡単に書けたにもかかわらず、後から登場したはずのJavaはfor文と列挙オブジェクトを併用して、手動でFor Each文相当のコードを書くことを要求した。もちろん、それでは実用的ではないので、後からFor Each文相当の構文が導入されたが、明らかに動きが遅い。

 それとは別に、自由度の拡大がワナを増やすという事例もある。Visual BasicのFor文で数字をカウントする場合、

For i = 0 To 9
  ……
Next i

といった表記となり、必ず特定の変数(この場合はi)を使って、必ず1ずつ増えつつ、指定された数の範囲を数えることしかできなかった。

 それに対して、C言語とその構文を引き継ぐ言語のfor文は極めて自由度が高い。for文の内部に書き込む3つの式(初期化、継続条件、繰り返しに当たる)を自由に書くことができるので、単純に変数が増減するという以外の繰り返し、例えばファイルが終わるまで連続して読み込むといった繰り返しを書くことができる。

 だが、自由度の高さは単純なミスを自動的に排除できないことを意味する。例えば、以下のようなバグ(変数「i」と間違えて「j」と書いている)はコンパイル時に確実に排除できない。

for ( i = 0; i <= 9; j++ )
{
  ……
}

 さて、C# 1.xは、自由度の高いC言語風のfor文と、自由度は低いが安全かつ楽ができるforeach文を持ち、割とバランスの良い言語だったといえる。筆者の個人的な感想からいえば、繰り返しのかなりの割合はforeach文で記述でき、残ったイレギュラーな繰り返しはfor文やdo文/while文で十分に対処できた。

 ただし、C# 1.xの「割と使えるバランスの良さ」には1つの大きな落とし穴があった。それは、foreach文で列挙可能なオブジェクトを作るのが割と面倒くさい……という問題である。

 クラス・ライブラリのオブジェクトを使って列挙しているだけならあまり気にならないが、自作クラスを列挙可能にしようと思うと、大きな面倒と手間を要求されてしまったのである。

 C# 2.0はその問題に対処するための「反復子」という機能を導入している。今回は主にそれについて解説する。

数を数えるというサンプル

 今回は指定した数を順番に数える列挙オブジェクトをサンプルに使ってみよう。これは例えば「0から9まで」と指定すると、0、1、2、3、4、5、6、7、8、9という整数の列を作り出す。

 元データなしで、無から列挙対象を創出することになるので、データをコレクションに入れて列挙すればよい……という対処方法が採れない題材である。

 ここまで読んで、「実用性のないサンプルのためのサンプルか……」と思った読者は考えを改めていただきたい。実は、この題材は、自由度が高すぎるfor文に対する1つの処方せんになる。

 例えば、以下のような変数名の書き間違いがfor文にはあり得ることをすでに書いた。

for ( i = 0; i <= 9; j++ )
{
  ……
}

 しかし、1つの変数を使って、一定回数を数えるのはプログラムでよく見る書き方である。Visual BasicのFor文なら、確実に1つの変数を使ってカウントしてくれる。

For i = 0 To 9
  ……
Next i

 だが、いまさら新しい構文を言語に増やすのも面倒な話である。既存の構文を使って、うまくやる方法はないだろうか。その答えの1つが、数を数える列挙オブジェクトである。

 例えばRubyには、言語にビルドインされたそのようなオブジェクトが存在する。以下の例は、0から9まで数えるオブジェクトを生成し、それによって変数iを0から9まで変化させながら繰り返す構文である。

for i in 0..9
   ……
end

 同じことを行うオブジェクトを、C#でも作成することができる。例えば、Rangeというクラス名だとすれば、以下のように記述して同じ機能を得ることができる。

foreach (int i in new Range(0, 9))
{
  ……
}

 ここでは、上記のような使い方ができるRangeクラスを記述することを題材にしてみる。

 余談だが、1つだけ補足しておこう。ここでRubyを説明の例に出したのは、Rubyが優秀な言語であり、C#は努力によってRubyに近づくことができる……という主張を行うためではない。Rubyは面白い言語ではあるが、本来の能力から見ると、最近の社会的な評価は過剰に高すぎるという印象がある。逆に、C#は本来の能力から見ると、社会的な評価が過剰に低すぎる感がある。筆者の漠然とした印象からいえば、個別の機能ではなく総合力で見ればRubyよりもC# 2.0の方がはるかに強力であり、実用性は高いと感じる。

C# 1.xによるRangeクラスの実装

 まず、C# 1.xでRangeクラスを作成してみよう(リスト1。実際にはVisual Studio 2005でC# 2.0の機能を使わないで記述し、動作を確認している)。

 いちおう正攻法で記述してみたので、かなり冗長になっている(列挙関係のインターフェイスを明示的に実装すれば、もっと文字数は増える)。

using System;

class RangeEnum
{
  private int current, to;

  public bool MoveNext()
  {
    current++;
    return current <= to;
  }

  public int Current
  {
    get { return current; }
  }

  public RangeEnum(int from, int to)
  {
    current = from - 1;
    this.to = to;
  }
}

class Range
{
  private int from, to;

  public RangeEnum GetEnumerator()
  {
    return new RangeEnum(from, to);
  }

  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
  }
}
リスト1 C# 1.xによるRangeクラスの実装

 このコードで目立つ“うっとうしい”部分は以下の2つだろう。

  • Rangeクラスのほかにもう1つRangeEnumクラスがあり、クラスを2つも書くのはうっとうしい
  • 列挙するためにMoveNextメソッド、Currentプロパティ、状態を記憶しておくフィールドcurrent、終了条件を保存しておくフィールドtoと盛りだくさんを書くのはうっとうしい

 このうっとうしさが、列挙可能なオブジェクトを自作するうっとうしさそのものといえる。


 INDEX
  C# 2.0入門
  第3回 新しい繰り返しのスタイル − yield return文とForEachメソッド
  1.繰り返しという古くて新しい問題/数を数えるというサンプル/C# 1.xによるRangeクラスの実装
    2.C# 2.0によるRangeクラスの実装/yeild break文による中断
    3.yieldは予約語ではない/1つのクラスに複数の列挙機能を付ける/自動的に作られるオブジェクトと2重利用/catchできない制約
    4.制約の真相:見た目と違う真実の姿/ForEachメソッドを使う別解/性能比較
 
インデックス・ページヘ  「C# 2.0入門」


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 記事ランキング

本日 月間