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されてほしいところですよね.これに関しては,実は自分もまだきちんと検証できてません(^^;;
明日の日記では検証結果を書く……かも?