特集

C# 2.0新機能徹底解説(後編)

進化したC# 2.0の状態管理、匿名メソッドとイテレータ

菊池 和彦
Microsoft Most Valuable Professional 2004 - Visual C#)
2004/12/28
Page1 Page2 Page3 Page4

■確実な後処理

 ここらでイテレータを記述するうえでの注意点についても触れておこう。

 注意点の1つは、呼び出し元のforeachループがbreak文などでそのループを脱出する場合だ。

 例えば以下のサンプル・コードは、一見して正しそうであるが重大な問題がある。

class itrSample2
{
  string pathname;
  public itrSample2(string pathname) { this.pathname = pathname; }
  public IEnumerator<string> GetEnumerator()
  {
    Encoding enc =Encoding.GetEncoding("Shift_JIS");
    StreamReader reader = new StreamReader(pathname,enc);
    string s;
    while( (s = reader.ReadLine())!=null ){ yield return s; }
    reader.Close();
  }
}
問題のあるイテレータの利用例

 なぜ問題があるのか確認するために、生成される入れ子クラスのDisposeメソッドの部分を逆アセンブルしてそのMSILコードを見てみよう。

method private hidebysig newslot virtual final
      instance void  System.IDisposable.Dispose() cil managed
{
  .override [mscorlib]System.IDisposable::Dispose
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
}
生成されたIDisposableインターフェイスのDisposeメソッド実装部分のMSILコード

 上記コードを見ると、Disposeメソッドは何もせずにリターンしている。そのため、呼び出し元がbreak文により途中でループを抜けてしまう場合、Closeメソッドが呼び出されずに適切な終了処理が行われないことになる。

 それでは、try/finally文を利用した場合はどうなるのだろうか。以下は、C#によりそれを実装したコードである。

public IEnumerator<string> GetEnumerator()
{
  Encoding enc =Encoding.GetEncoding("Shift_JIS");
  StreamReader reader = new StreamReader(pathname, enc);
  try
  {
    string s;
    while ((s = reader.ReadLine()) != null) { yield return s; }
  }
  finally { reader.Close(); }
}
問題のないイテレータの利用例

 これを同じようにMSILコードへ逆アセンブルすると以下のようになる。

method private hidebysig newslot virtual final
      instance void  System.IDisposable.Dispose() cil managed
{
 ……省略……
    IL_002b:  ldfld      class System.IO.StreamReader
               itrSample2/ '<GetEnumerator>d__0'::'<reader>5__2'
    IL_0030:  callvirt   instance void System.IO.TextReader::Close()
 ……省略……
}
生成されたIDisposableインターフェイスのDisposeメソッド実装(一部省略)のMSILコード

 上記のMSILコードのDisposeメソッドを確認すると、finally文の内容が出力されているのが分かる。

 では、using文を使った場合はどうだろうか。これも同様にDisposeメソッドによって後処理が行われることが確認できるはずだ。これについては、コードを掲載しないので、ぜひ各自で実行して確認してほしい。

 以上のように、try/finally文やusing文によって後処理が必要なことを明示すれば、C#コンパイラはそれに従った後処理を適切な場所に置いてくれる。しかし後処理の必要性を明示しなければ、「yield return」文で呼び出し元に戻った場合に、break文などでforeachループを抜け出してしまうと後処理は実行されないので注意すること。

■イテレータのまとめ

 イテレータは、コンパイラによるコード生成で起こるちょっとした変化でしかない。匿名メソッドで出てきたローカル変数のフィールド変数化と暗黙のクラス生成を基盤として、実行ポイントへの番号振り、抜け出し時の番号の保存、抜け出し点の直後へ飛び込むためのコード生成。この程度の変化だ。

 しかしこれは、プログラマにとっては大きな変化である。イテレータは、開発経験の長いプログラマほど不可解なものに見えるようだ。実際に、Microsoft MVP同士の会話を聞いてもその不可解さを感じている人が少なくない。今回の記事で、その不可解さは氷解しただろうか。

 それでは、先ほどのタイトルのとおりに、「C#は構文の意味を理解する言語に進化した」のであろうか。

 構文の意味を理解しているのは、もともとどんなコンパイラでもそうだ。「yield return」文によって関数の途中で抜け出すことやその場に戻る機能は、関数をバラバラに分解して意味を維持したまま変形しているような印象を与える。しかし、それは錯覚だ。関数を抜け出す個所に番号を振り、どこから抜け出したのかを管理し、再度そこに戻るMSILコードが生成されているだけだ。

 しかし、コンパイラが意味を理解しているということについての再認識は重要だ。後処理が必要だということは、プログラム・コードにその意味を込めて適切にtry/finally文やusing文を実装しなければならない。後処理のコードを書いていても、try/finally文で後処理だという意味を適切に与えなければ、当然、コンパイラにはその意味を理解してもらえない。プログラマがコードに込めた意味が足りなければ、コンパイラは意味をくみ取れずに、足りない意味に沿ったコードが生まれるのである。

今回のまとめ

 匿名メソッドとイテレータでの変化の最も大きな点は、冒頭でも述べたように状態管理機能の強化だといえるだろう。両者ともにネストしたクラスを生成することで、小規模な状態を保持する仕組みを持っている。

 このため、プログラマは状態管理を行う小さなクラスを作る必要がなくなる。状態管理が必要な場面で小さなクラスが必要だった局面のほとんどは、匿名メソッドで用が足りるようになるだろう。

 イテレータを使えば、内部で実行点の管理を行ってくれるので、複雑になりがちだった状態マシン(自分の状態に応じて応答の仕方が異なるクラス)の記述や、コードの単純さのために作られていた入出力ループ(これまでは多数の入出力デバイスがある場合に、それぞれの状態を管理するのは非常に困難であった。この各デバイスを個別スレッドで対応することで実行ポイントの管理をスレッドで代替するなどの方法を取っていた例も多いだろう)などのスレッドを減らせるようになるだろう。

 逆に、これらの状態管理機能を使うことで実装しなければならないクラスの数が減り、当然としてクラス名などのシンボルが減ることになる。クラス名に意味のある名前を付けていたために読み取れていたコードが、シンボルが減ることで読めないコードになる可能性も十分にあるだろう。クラス名やメソッド名の命名が面倒だからという理由で、これらの機能を乱用すれば、コードの可読性を落とすことになるので、不必要な匿名メソッドやイテレータの利用は避けるべきだ。

■最後に

 前編、後編の2回に分けてC# 2.0の主要な拡張点であるジェネリック、匿名メソッド、イテレータについては触れてきた。残念ながらそれ以外の細かな改良点に触れるスペースは残っていないが、来る2005年にはVisual Studio 2005がリリースされる(と思いたい)し、そのベータ版はすでに入手可能だ。この年末の休みにはVisual Studio 2005のベータ版をインストールして、いろいろなコードをコンパイルしてはILDASMで眺め回してみるのはどうだろうか。きっと面白い発見があるだろう。End of Article

 

 INDEX
  [特集] C# 2.0新機能徹底解説(前編)
  開発生産性を飛躍的に高めるジェネリック
    1.ジェネリックとは?
    2.ジェネリック・コレクションの利用
    3.汎用アルゴリズムを実装しているList<T>クラスのメンバ
    4.ジェネリックを使ったコレクション・クラスの拡張
  [特集] C# 2.0新機能徹底解説(後編)
  進化したC# 2.0の状態管理、匿名メソッドとイテレータ
    1.匿名メソッドとその正体
    2.イテレータの衝撃!
    3.イテレータを解剖する
  4.確実な後処理
 


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

本日 月間