連載:[完全版]究極のC#プログラミングChapter3 新しい繰り返しのスタイル ― yield return文とForEachメソッド川俣 晶2009/08/31 |
|
|
3.10 制約の真相―見た目と違う真実の姿
これまで、反復子ブロックを使った際のいくつかの制約について見てきたが、それらについてはすべて「反復子ブロックは普通のブロックではないから」と説明した。では、具体的にどう違うのだろうか?
その疑問に答えるのは難しくない。
見かけ上の姿と真の姿の差は、逆コンパイラツール「Reflector」を使うことで簡単に見ることができるからだ。
実際に、リスト3.2をデバッグビルドした実行ファイルをReflector for .NETによりC#のソースコードとして表示させた例を以下に示す。
まず、Rangeクラス内に生成されている、元のソースコードになかった「<GetEnumerator>d__0」というクラスのメンバー一覧(リスト3.10)を見てみよう。そこには、C# 1.x時代におなじみだったMoveNextメソッドやCurrentプロパティが生成されていることがわかると思う。
| |
リスト3.10 自動生成された<GetEnumerator>d__0クラスのメンバー一覧 |
このクラスの中で、MoveNextメソッドの内容だけ見てみることにしよう(リスト3.11参照)。
| |
リスト3.11 MoveNextメソッドの内容(リスト3.2の逆コンパイル結果の一部) |
ちなみに、このコードは、一応C#の文法に沿って表記されているとはいえ、読みにくいので簡単に説明しておくと、変数「this.<>1__state」には、繰り返しの継続を行うか否かの情報が保存されている。この値をswitch文で判定し、0なら繰り返しの開始を行い、1なら繰り返しの途中に戻って継続するという処理を行う。そして、変数「this.<i>5__1」は繰り返しの回数をカウントするために使われる。繰り返しが終了すると、「this.<>1__state」は-1という値でwhile文を抜けることになるが、この-1はもはやswitch文で何もアクションを起こさない“繰り返しが終わった”ことを示す値となる。
しかし、「<」や「>」のような表面的にコンパイルできない表記は別として考えても、このコードは根本的にC#の文法に反していてコンパイルできない。whileループの途中でreturn文により戻り、次に呼ばれたときにgoto文でループの途中へ戻るようなコードは、C#では許されない(このコードは実際にはC#ではなくIL*3で記述されていて、ILレベルでは正しいコードになっている。C#で許されないコードが生成されてしまうのは、Reflector for .NETがILのコードを強制的にC#に翻訳しているため)。
しかし、このようなコードを見ると、反復子という機能が実はシンタックスシュガー(糖衣構文*4)にすぎないことがよくわかるだろう。
つまり、反復子とは、短いソースコードから本来あるべき長いソースコードへの翻訳サービスそのものなのである。その過程で、元のソースは切り刻まれ、似ても似つかない形に変形される。反復子に課せられた制約とは、このC# 3.0コンパイラによる変換作業の都合によって課せられた制約だといえる。
たとえば、try〜catch構文でyield return文が使用できないのは、途中で実行を打ち切って、後から続きを継続実行するようなコードとうまく整合しないためだろう。しかし、try〜finally構文のほうは、IDisposeインターフェースをうまく活用し、実質的にfinallyブロック相当の機能を実現することで利用可能としている。
このあたりについては、いろいろなコードを作成してからReflector for .NETで調べてみると、興味深いことがわかる。ストレートに、C# 3.0言語仕様を見てもよい。そこには、多くの機能と制約が詳しく書かれている。C# 3.0言語仕様は、Visual Studio 2008をインストールしている場合には、Visual Studio 2008のインストールディレクトリ配下の「VC#\Specifications\1041\CSharp Language Specification.doc」にある。
*3 正確にはCLI. MSILとも呼ばれる。.NET Frameworkの仮想アセンブラ言語である。 *4 より簡単、かつ、わかりやすい別の書き方を提供するための構文上のサービスのこと。 |
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#プログラミング」 |
- 第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用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|