yieldを使いました
世間ではC#だとかLINQだとかSilverlightだとか盛り上がっているというのにアレですが,仕事ではじめてyieldを使ってみました.知ってはいたんですけどね,使うシチュエーションがなかったんですよ.
どんな場面で使ったかというと,インターネット越しにあるファイルをダウンロードしたいとき.NETにはHttpWebRequestクラスが使えますね.使い方はこんな感じでしょうか.
using System.Net; using System.IO; class Program { static void Main(string[] args) { string fetchUrl = @"http://d.hatena.ne.jp/images/diary/k/kurip/kurip.jpg"; string outputFilename = @"C:\work\kurip.jpg"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fetchUrl); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { using (Stream stream = response.GetResponseStream()) { using (BinaryReader binaryReader = new BinaryReader(stream)) { byte[] buffers = new byte[1024]; using (FileStream fileStream = new FileStream(outputFilename, FileMode.Create, FileAccess.ReadWrite)) { int readnum = 0; while ((readnum = binaryReader.Read(buffers, 0, buffers.Length)) > 0) { fileStream.Write(buffers, 0, readnum); } } } } } } }
ネストが深いですね.なんとかならないでしょうか?
まず,Stream.CloseとHttpWebResponse.Close の両方を呼び出す必要はありません,とMSDNに書いてある(http://msdn2.microsoft.com/ja-jp/library/system.net.httpwebresponse.close(VS.80).aspx)ので,HttpWebResponseのほうのusingを取り去ってみましょう.あと,ご存知の通りusingは縦に並べることが出来ます.(やや恣意的ですが)using Streamとusing FileStreamを並べてみます.すると以下のようになります.
class Program { static void Main(string[] args) { string fetchUrl = @"http://d.hatena.ne.jp/images/diary/k/kurip/kurip.jpg"; string outputFilename = @"C:\work\kurip.jpg"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fetchUrl); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); using (Stream stream = response.GetResponseStream()) using (FileStream fileStream = new FileStream(outputFilename, FileMode.Create, FileAccess.ReadWrite)) { using (BinaryReader binaryReader = new BinaryReader(stream)) { byte[] buffers = new byte[1024]; int readnum = 0; while ((readnum = binaryReader.Read(buffers, 0, buffers.Length)) > 0) { fileStream.Write(buffers, 0, readnum); } } } } }
だいぶネストを浅くすることが出来ました.
BinaryReaderについては,本当に必要かどうかは微妙だったりします.Streamから直接Readするサンプルも結構あったりするんですよね.自分にはその違いがわからないのでそこはそのまま残すとして,まだなんとなくごちゃごちゃ見えるのはやはり,byte配列に対してReadしてWriteして,ってところではないでしょうか?ここをどんなメソッドで切り出しましょうか?
SaveTo(Stream stream, FileStream fileStream)みたいなメソッドを作るのも1つの手でしょうが,感覚的に嫌な感じがしました.そうだ,yieldだ!
class Program { static void Main(string[] args) { string fetchUrl = @"http://d.hatena.ne.jp/images/diary/k/kurip/kurip.jpg"; string outputFilename = @"C:\work\kurip.jpg"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fetchUrl); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); using (Stream stream = response.GetResponseStream()) using (FileStream fileStream = new FileStream(outputFilename, FileMode.Create, FileAccess.ReadWrite)) { foreach(byte[] bytes in GetBytes(stream)) { fileStream.Write(bytes, 0, bytes.Length); } } } static IEnumerable<byte[]> GetBytes(Stream stream) { using (BinaryReader binaryReader = new BinaryReader(stream)) { byte[] buffers = new byte[1024]; int readnum = 0; while ((readnum = binaryReader.Read(buffers, 0, buffers.Length)) > 0) { byte[] returnBuf = new byte[readnum]; Array.Copy(buffers, returnBuf, readnum); yield return returnBuf; } } } }
ずいぶんとすっきりしたように見えます.長年の夢(?)だったyieldも使えたことだし.めでたしめでたし,かな?
ただ1つ気になることがあります.こんな感じでyieldを使ってしまって,BinaryReaderのClose(Dispose)は,どんなときでも正しく呼び出されるのでしょうか?正常終了時または例外発生時でも,正しくCloseされてほしいところですよね.これに関しては,実は自分もまだきちんと検証できてません(^^;;
明日の日記では検証結果を書く……かも?