パイプラインパターンとリソース管理
このごろ傾倒してるパイプラインパターン。C#3.0のメソッド拡張構文やLINQや関数言語型のような宣言型のプログラミングを見据えていろいろ模索しています。
部品を細かく分けて変更に強い構造にできる利点は非常に大きいと思っています。例えば、たびたび例に出しているファイルから1行づつ読み取るパターンですが、データのソースがファイルからコレクションやほかの何かに変更になっても影響が小さいですね。
また、汎用的な部品、例えば IEnumerable
もっともこのあたりは、RubyやPythonのような言語を使いこなしている人にとっては今更な話題かもしれません。
今日のレシピはリソース管理。Disposeがきちんと呼ばれるか実験です。Ouchメソッドでわざと例外を出して、TextFileReaderのDisposeが呼ばれるよね?という確認です。
リソース管理構文のusingが外側のループの例外発生に対して有効なのかどうか、です。また、OuchをOuch2に置き換えるとどうなるか?もお試しです。
using System; using System.Collections.Generic; using System.Text; using System.IO; public class Program { public static void Main() { const string fileName = "text.txt"; try { foreach ( string str in Ouch( TextFileReader( fileName ) ) ) { Console.WriteLine( str ); } } catch ( Exception e ) { Console.WriteLine( e.Message ); } Console.ReadLine(); } private static IEnumerable<string> Ouch( IEnumerable<string> input ) { int count = 0; foreach ( string s in input ) { count++; if ( count > 2 ) throw new Exception(); yield return s; } } private static System.Collections.IEnumerable Ouch2( System.Collections.IEnumerable input ) { System.Collections.IEnumerator it = input.GetEnumerator(); it.MoveNext(); yield return it.Current; throw new Exception(); } public static IEnumerable<string> TextFileReader( string fileName ) { using ( StreamReader2 sr = new StreamReader2( fileName ) ) { string line; while ( ( line = sr.ReadLine() ) != null ) yield return line; } } } public class StreamReader2 : StreamReader { public StreamReader2( string fileName ) : base( fileName ) { } protected override void Dispose( bool disposing ) { Console.WriteLine( "StreamReader2:Dispose" ); base.Dispose( disposing ); } }
OuchではDisposeが呼ばれますね。対してOuch2では呼ばれません。解決策としてはOuch2内でもOuch同様foreachを使うのが一番簡単ですね。
ぜひ、ReflectorでOuchとOuch2を覗いて比べてみてください。いろいろと発見があると思います。
パイプラインパターンはC#3.0を待たなくても、今すぐ使えるパターンですね。ループを超えた最適化をしてくれればもっといいのになぁ。
(PLINQなんてのも見据えているけど、あまり期待はしていない(^^; GoogleのMapReduce的な発想ですね)