色んな事を書く

シンプルさを極めたエンジニアになりたい

Stream の位置を初期化する方法

Stream を使いまわしたい時に Stream の Potision を初期化する方法です。

例えばですが Stream を引数にとって取り扱うメソッドやクラスの単体テストを書きたいとします。Arrange-Act-Assert パターンで書くとして、Arrange のフェーズではテスト対象メソッドに渡すための Stream を初期化するでしょう。そしてその Stream にテスト用データを書き込んでいくと。んで、その Stream をメソッドに渡して Act フェーズに入ると思います。

例えばこんなテストコードですね (MemoryStream の初期化時にテストデータを入れればいいやんけ、というのはいったんスルーさせていただきます)。

public static void Test()
{
    
    var text = "Hello World";
    using var stream = new MemoryStream();

    using var writer = new StreamWriter(stream, leaveOpen: true);
    writer.WriteLine(text);
    writer.Flush();

    using var reader = new StreamReader(stream, leaveOpen: true);
    var result = reader.ReadLine();
    
    Console.WriteLine(result); // null
}

この場合、テスト用データを書き込んだ際に Stream の Potision が移動してしまうので、テスト対象メソッドの戻り値が意図した挙動にならない可能性があります。このテストの場合は result の値が Null となってしまいます。なので、Stream を先頭から取り扱ってもらうには Potision を先頭に戻してあげる必要があります。

2 パターンあります。

  1. Position を使って初期化する方法
  2. Seek を使って初期化する方法

です。まずは Position を使う方法からです。

public static void Test()
{
    var text = "Hello World";
    using var stream = new MemoryStream();

    using var writer = new StreamWriter(stream, leaveOpen: true);
    writer.WriteLine(text);
    writer.Flush();
    
    // reset stream potition
    stream.Position = 0;
    
    using var reader = new StreamReader(stream, leaveOpen: true);
    var result = reader.ReadLine();
    
    Console.WriteLine(result); // -> Hello World
}

stream の Potition を明示的に 0 (初期値) にする方法です。

続いて Seek() を使うパターンです。

public static void Seek()
{
    var text = "Hello World";
    using var stream = new MemoryStream();

    using var writer = new StreamWriter(stream, leaveOpen: true);
    writer.WriteLine(text);
    writer.Flush();
    
    // reset stream potition
    stream.Seek(0, SeekOrigin.Begin);
    
    using var reader = new StreamReader(stream, leaveOpen: true);
    var result = reader.ReadLine();
    
    Console.WriteLine(result);
}

こんな感じになりますね。Seek の第一引数はオリジン (第二引数で指定する) からの相対位置を示す offset です。第二引数は Stream のどの位置を基準に取るのかを指定するものです。取りうる値としては

  • Begin (Stream の先頭を示す)
  • Current (Stream の現在位置を示す)
  • End (Stream の末尾を示す)

の 3 種類があります。サンプルの例では Begin を基準にして offset が 0 なので、Stream の先頭になるということですね。

ちなみにですが今回の例ですと Seek の場合でも Position を 0 にするような実装になっているので、実質的には同じようです。

case SeekOrigin.Begin: {
    int tempPosition = unchecked(_origin + (int)offset);
    if (offset < 0 || tempPosition < _origin)
        throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
    _position = tempPosition;
    break;
}

github.com

まとめると

  • Position は Stream の絶対的な位置を指定出来る
  • Seeek は Stream の相対的な位置を指定できる

ということになります。