BinaryReaderクラスおよびBinaryWriterクラスはStreamに対してバイナリデータの読み書きを行うためのクラスです。 Stream単体ではバイト配列での読み書きしか行えませんが、StreamとBinaryReader・BinaryWriterを組み合わせて使うことで構造化されたバイナリデータの読み書きが可能になります。

BinaryReader・BinaryWriterを使ったバイナリファイルの読み書き
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイルsample.datを書き込み用に開く
    using (Stream stream = File.OpenWrite("sample.dat")) {
      // streamに書き込むためのBinaryWriterを作成
      using (BinaryWriter writer = new BinaryWriter(stream)) {
        // intの数値を書き込む
        writer.Write((int)42);

        // 4バイトのバイト配列を書き込む
        writer.Write(new byte[] {0x01, 0x23, 0x45, 0x67});
      }
    }

    // ファイルsample.datを読み込み用に開く
    using (Stream stream = File.OpenRead("sample.dat")) {
      // streamから読み込むためのBinaryReaderを作成
      using (BinaryReader reader = new BinaryReader(stream)) {
        // intの数値を読み込む
        Console.WriteLine(reader.ReadInt32());

        // 4バイト読み込む
        byte[] data = reader.ReadBytes(4);

        Console.WriteLine(BitConverter.ToString(data));
      }
    }
  }
}
BinaryReader・BinaryWriterを使ったバイナリファイルの読み書き
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' ファイルsample.datを書き込み用に開く
    Using stream As Stream = File.OpenWrite("sample.dat")
      ' streamに書き込むためのBinaryWriterを作成
      Using writer As New BinaryWriter(stream)
        ' Integerの数値を書き込む
        writer.Write(CInt(42))

        ' 4バイトのバイト配列を書き込む
        writer.Write(New Byte(3) {&h01, &h23, &h45, &h67})
      End Using
    End Using

    ' ファイルsample.datを読み込み用に開く
    Using stream As Stream = File.OpenRead("sample.dat")
      ' streamから読み込むためのBinaryReaderを作成
      Using reader As New BinaryReader(stream)
        ' intの数値を読み込む
        Console.WriteLine(reader.ReadInt32())

        ' 4バイト読み込む
        Dim data() As Byte = reader.ReadBytes(4)

        Console.WriteLine(BitConverter.ToString(data))
      End Using
    End Using
  End Sub
End Class

BinaryReader・BinaryWriterでは、読み書きの際のバイトオーダにリトルエンディアンを使用します。 実行環境のバイトオーダによらず、常にリトルエンディアンでの読み書きが行われます。

なお、.NET Frameworkにはビッグエンディアンで読み書きを行うBinaryReader・BinaryWriterは用意されていません。 BinaryReader・BinaryWriterが使用するバイトオーダを指定したり変更することもできません。 ビッグエンディアンでの読み書きを行うには、バイトオーダの変換などを自前で実装する必要があります。 この点については、ランタイム・システム・プラットフォームの情報 §.エンディアン基本型の型変換 §.基本型とバイト配列への/からの変換などを参照してください。

BinaryReader

基本型の読み込み (ReadInt32, etc)

BinaryReaderには、基本型の読み込みを行うメソッドがいくつか用意されています。

ReadInt32等を使ってストリームから基本型の値を読み込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // 整数型の値の読み込み
        int i = reader.ReadInt32();

        uint ui = reader.ReadUInt32();

        byte by = reader.ReadByte();

        // 実数型の値の読み込み
        float f = reader.ReadSingle();

        double d = reader.ReadDouble();

        // bool型の値の読み込み
        bool b = reader.ReadBoolean();
      }
    }
  }
}
ReadInt32等を使ってストリームから基本型の値を読み込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' 整数型の値の読み込み
        Dim i As Integer = reader.ReadInt32()

        Dim ui As UInteger = reader.ReadUInt32()

        Dim by As Byte = reader.ReadByte()

        ' 実数型の値の読み込み
        Dim s As Single = reader.ReadSingle()

        Dim d As Double = reader.ReadDouble()

        ' Boolean型の値の読み込み
        Dim b As Boolean = reader.ReadBoolean()
      End Using
    End Using
  End Sub
End Class

BinaryReaderに用意されているメソッドと、読み込める基本型は次のとおりです。 ほとんどの.NET Frameworkのプリミティブ型に対応する読み込みメソッドが用意されています。

基本型の読み込みを行うメソッド
メソッド 読み込めるデータ型 読み込まれるデータのサイズ
ReadSByte sbyte, SByte 1バイト
ReadInt16 short, Short 2バイト
ReadInt32 int, Integer 4バイト
ReadInt64 long, Long 8バイト
ReadByte byte, Byte 1バイト
ReadUInt16 ushort, UShort 2バイト
ReadUInt32 uint, UInteger 4バイト
ReadUInt64 ulong, ULong 8バイト
ReadSingle float, Single 4バイト
ReadDouble double, Double 8バイト
ReadDecimal decimal, Decimal 16バイト
ReadChar char, Char 2バイト
ReadBoolean bool, Boolean 4バイト
メソッド 読み込めるデータ型 読み込まれるデータのサイズ

すでに述べた通り、これらのメソッドで複数バイトのデータを読み込む場合、リトルエンディアンで読み込まれます。

BinaryReaderにはビット単位での読み込みを行うメソッドは用意されていません。

BinaryReaderにはDateTimeやDateTimeOffsetを読み込むメソッドも用意されていません。 DateTime・DateTimeOffsetの読み書きを行う場合はDateTime.ToBinary・DateTime.FromBinaryなどのメソッドを使って数値として扱う必要があります。

これらのメソッドでストリームの残りバイト数よりも多いバイト数を読み込もうとした場合(読み込むことによってストリームの末尾を超えてしまう場合)には、例外EndOfStreamExceptionがスローされます。 この際、読み込みに失敗してもストリームの現在位置は読み込む前の位置には戻りません。

BinaryReaderでの読み込みによってストリームの末尾を超える場合
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // 全6バイトのデータ
    byte[] data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xab};

    using (Stream stream = new MemoryStream(data)) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // int(4バイト)を読み込む
        int i = reader.ReadInt32();

        // ストリームの現在位置を表示
        Console.WriteLine(reader.BaseStream.Position);

        try {
          // さらにもう一度int(4バイト)を読み込もうとする
          // (実際に4バイト読み込むとストリームの末尾を超えてしまうため、例外EndOfStreamExceptionがスローされる)
          i = reader.ReadInt32();
        }
        catch (EndOfStreamException) {
          Console.WriteLine("end of stream");
        }

        // ストリームの現在位置を表示
        Console.WriteLine(reader.BaseStream.Position);
      }
    }
  }
}
BinaryReaderでの読み込みによってストリームの末尾を超える場合
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 全6バイトのデータ
    Dim data() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hAB}

    Using stream As Stream = New MemoryStream(data)
      Using reader As New BinaryReader(stream)
        ' Integer(4バイト)を読み込む
        Dim i As Integer = reader.ReadInt32()

        ' ストリームの現在位置を表示
        Console.WriteLine(reader.BaseStream.Position)

        Try
          ' さらにもう一度Integer(4バイト)を読み込もうとする
          ' (実際に4バイト読み込むとストリームの末尾を超えてしまうため、例外EndOfStreamExceptionがスローされる)
          i = reader.ReadInt32()
        Catch ex As EndOfStreamException
          Console.WriteLine("end of stream")
        End Try

        ' ストリームの現在位置を表示
        Console.WriteLine(reader.BaseStream.Position)
      End Using
    End Using
  End Sub
End Class
実行結果
4
end of stream
6

バイト配列の読み込み (ReadBytes, Read)

ストリームから複数バイトを読み込みバイト配列として取得するにはReadBytesメソッドを使うことができます。 このメソッドでは、読み込みたいバイト数を引数に指定します。

ReadBytesメソッドを使って指定したバイト数分をストリームから読み込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // ストリームから16バイト読み込みバイト配列として取得する
        byte[] data = reader.ReadBytes(16);

        // 読み込んだバイト配列の内容を表示する
        Console.WriteLine(BitConverter.ToString(data));
      }
    }
  }
}
ReadBytesメソッドを使って指定したバイト数分をストリームから読み込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' ストリームから16バイト読み込みバイト配列として取得する
        Dim data() As Byte = reader.ReadBytes(16)

        ' 読み込んだバイト配列の内容を表示する
        Console.WriteLine(BitConverter.ToString(data))
      End Using
    End Using
  End Sub
End Class

上記の例で使用しているBitConverter.ToStringメソッドはバイト配列を見やすい形式に変換するためのもので、ReadBytesメソッドを使った読み込み処理の本質とは無関係のものです。

ReadBytesメソッドでは、ストリームの残りバイト数よりも多いバイト数を読み込もうとした場合には実際に読み込めた分のみが返されます。 読み込み中にストリームの末尾に達した場合でもEndOfStreamExceptionはスローされません。 実際に読み込めたバイト数は返されるバイト配列の長さを調べることで知ることができます。 例えば、16バイト読み込もうとして実際には残り8バイトだった場合には、長さ8のバイト配列が返されます。 ストリームの末尾に達してそれ以上読み込めるデータがない場合、ReadBytesメソッドは長さ0のバイト配列を返します。

もうひとつ、バイト配列に読み込むメソッドとしてReadメソッドも用意されています。 こちらはReadBytesメソッドとは異なり、あらかじめ用意したバイト配列を指定することにより、読み込んだデータをその配列へ格納します。 Readメソッドは、Stream.Readメソッドと同様に指定したバイト数の分だけ読み込みを試みますが、指定したバイト数ちょうどのデータが一度に読み込まれるとは限りません。 読み込み中にストリームの末尾に達した場合でもEndOfStreamExceptionはスローされません。 実際に読み込めたバイト数は戻り値で知ることができます。 ストリームの末尾に達していてそれ以上読み込めるデータがない場合、Readメソッドは0を返します。

Readメソッドを使ってストリームから指定したバイト配列にデータを読み込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // 8バイトのデータ
    byte[] data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};

    using (Stream stream = new MemoryStream(data)) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // 読み込んだデータを格納するためのバッファ
        byte[] buffer = new byte[16];

        // ストリームから最大6バイトを読み込み、buffer[0]以降に格納する
        int len = reader.Read(buffer, 0, 6);

        // 実際に読み込めた分を表示する
        Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len));

        // ストリームから最大6バイトを読み込み、buffer[0]以降に格納する
        len = reader.Read(buffer, 0, 6);

        // 実際に読み込めた分を表示する
        Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len));
      }
    }
  }
}
Readメソッドを使ってストリームから指定したバイト配列にデータを読み込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 8バイトのデータ
    Dim data() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hab, &hcd, &hef}

    Using stream As Stream = New MemoryStream(data)
      Using reader As New BinaryReader(stream)
        ' 読み込んだデータを格納するためのバッファ
        Dim buffer(15) As Byte

        ' ストリームから最大6バイトを読み込み、buffer(0)以降に格納する
        Dim len As Integer = reader.Read(buffer, 0, 6)

        ' 実際に読み込めた分を表示する
        Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len))

        ' ストリームから最大6バイトを読み込み、buffer(0)以降に格納する
        len = reader.Read(buffer, 0, 6)

        ' 実際に読み込めた分を表示する
        Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len))
      End Using
    End Using
  End Sub
End Class
実行結果
6 01-23-45-67-89-AB
2 CD-EF

BinaryReaderにはStreamReader.ReadToEndメソッドのようなストリームの末尾までをひとつのバイト配列に読み込むメソッドは用意されていないため、必要な場合は自前で実装する必要があります。 次の例は、そのようなメソッドを実装した例です。 効率的な実装ではないため、あくまで参考程度のものです。

BinaryReaderを使ってストリームの末尾までをひとつのバイト配列に読み込むメソッドの実装例
using System;
using System.Collections.Generic;
using System.IO;

class Sample {
  // ストリームの末尾までを読み込みバイト配列として返すメソッド
  private static byte[] ReadToEnd(BinaryReader reader)
  {
    // ストリームの長さの取得を試みる
    long length;

    try {
      length = reader.BaseStream.Length;
    }
    catch (NotSupportedException) {
      // 長さの取得をサポートしていないストリームの場合
      length = -1;
    }

    if (length == -1) {
      // 長さを取得できない場合は、Readメソッドを使ってストリームの末尾まで読み込む

      // 一度のReadで読み込めたバイト配列を格納するリスト
      List<ArraySegment<byte>> buffers = new List<ArraySegment<byte>>();

      length = 0;

      for (;;) {
        byte[] buffer = new byte[1024];
        int len = reader.Read(buffer, 0, buffer.Length);

        if (len == 0)
          // ストリームの末尾に達した
          break;

        // 読み込めたデータを含むバイト配列をArraySegmentに格納してリストに追加
        buffers.Add(new ArraySegment<byte>(buffer, 0, len));

        length += len;
      }

      // すべてのArraySegmentを連結し、ひとつのバイト配列にして返す
      byte[] bytes = new byte[length];
      int index = 0;

      foreach (ArraySegment<byte> buffer in buffers) {
        Buffer.BlockCopy(buffer.Array, buffer.Offset, bytes, index, buffer.Count);

        index += buffer.Count;
      }

      return bytes;
    }
    else {
      // 長さを取得できた場合は、ReadBytesメソッドを使ってストリームの末尾まで読み込む
      return reader.ReadBytes((int)(length - reader.BaseStream.Position));
    }
  }

  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        byte[] bytes = ReadToEnd(reader);

        Console.WriteLine(BitConverter.ToString(bytes));
      }
    }
  }
}
BinaryReaderを使ってストリームの末尾までをひとつのバイト配列に読み込むメソッドの実装例
Imports System
Imports System.Collections.Generic
Imports System.IO

Class Sample
  ' ストリームの末尾までを読み込みバイト配列として返すメソッド
  Private Shared Function ReadToEnd(ByVal reader As BinaryReader) As Byte()
    ' ストリームの長さの取得を試みる
    Dim length As Long

    Try
      length = reader.BaseStream.Length
    Catch ex As NotSupportedException
      ' 長さの取得をサポートしていないストリームの場合
      length = -1
    End Try

    If length = -1 Then
      ' 長さを取得できない場合は、Readメソッドを使ってストリームの末尾まで読み込む

      ' 一度のReadで読み込めたバイト配列を格納するリスト
      Dim buffers As New List(Of ArraySegment(Of Byte))()

      length = 0

      Do
        Dim buffer(1024 - 1) As Byte
        Dim len As Integer = reader.Read(buffer, 0, buffer.Length)

        If len = 0 Then
          ' ストリームの末尾に達した
          Exit Do
        End If

        ' 読み込めたデータを含むバイト配列をArraySegmentに格納してリストに追加
        buffers.Add(New ArraySegment(Of Byte)(buffer, 0, len))

        length += len
      Loop

      ' すべてのArraySegmentを連結し、ひとつのバイト配列にして返す
      Dim bytes(CInt(length) - 1) As Byte
      Dim index As Integer = 0

      For Each buffer As ArraySegment(Of Byte) In buffers
        System.Buffer.BlockCopy(buffer.Array, buffer.Offset, bytes, index, buffer.Count)

        index += buffer.Count
      Next

      return bytes
    Else
      ' 長さを取得できた場合は、ReadBytesメソッドを使ってストリームの末尾まで読み込む
      Return reader.ReadBytes(CInt(length - reader.BaseStream.Position))
    End If
  End Function

  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        Dim bytes() As Byte = ReadToEnd(reader)

        Console.WriteLine(BitConverter.ToString(bytes))
      End Using
    End Using
  End Sub
End Class

上記のサンプル中で使用しているArraySegmentについては部分配列 §.ArraySegment構造体、Buffer.BlockCopyメソッドについてはバイト列操作 §.BlockCopyメソッドを参照してください。

なお、読み込み元がファイルに限定される場合はFileクラスのメソッドを使うこともできます。 File.ReadAllBytesメソッドを使うと、BinaryReaderを使わずにファイルの内容をバイト配列として読み込むことができます。

File.ReadAllBytesメソッドを使ってファイルの内容をバイト配列に読み込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイルsample.datを読み込み、バイト配列として取得する
    byte[] bytes = File.ReadAllBytes("sample.dat");

    Console.WriteLine(BitConverter.ToString(bytes));
  }
}
File.ReadAllBytesメソッドを使ってファイルの内容をバイト配列に読み込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' ファイルsample.datを読み込み、バイト配列として取得する
    Dim bytes() As Byte = File.ReadAllBytes("sample.dat")

    Console.WriteLine(BitConverter.ToString(bytes))
  End Sub
End Class

文字列の読み込み (ReadString)

ストリームから文字列を読み込むにはReadStringメソッドを使うことができます。

ReadStringメソッドを使ってストリームから文字列を読み込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // ストリームから文字列を読み込み表示する
        string s = reader.ReadString();

        Console.WriteLine(s);
      }
    }
  }
}
ReadStringメソッドを使ってストリームから文字列を読み込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' ストリームから文字列を読み込み表示する
        Dim s As String = reader.ReadString()

        Console.WriteLine(s)
      End Using
    End Using
  End Sub
End Class

ReadStringメソッドは、BinaryWriter.Writeメソッドで書き込まれる形式での読み込みを行います。 BinaryWrite.Writeメソッドで文字列を書き込む場合、先頭に文字列の長さが書き込まれます。 ReadStringメソッドはその長さを元に文字列の読み込みを行います。

従ってReadStringメソッドでは、読み込む文字列の長さを事前に知っている必要はなく、引数で読み込む文字列の長さを指定する必要もありません。 逆に、ReadStringメソッドを固定長の文字列フィールドを読み込む目的に使用することはできません。 固定長の文字列フィールドの読み込みを行う場合は、ReadBytesメソッドReadCharsメソッドを使って読み込み処理を記述する必要があります。 固定長の文字列フィールドを読み書きする具体例はBinaryWriter.Writeメソッドの解説をご覧ください。

ReadStringメソッドで文字列を読み込む際、デフォルトではUTF-8でデコードして読み込みますが、BinaryReaderのコンストラクタで目的のEncodingを指定することで任意のエンコーディングで文字列を読み込むことができます。 当然、BinaryWriter.Writeメソッドで書き込んだときと同じエンコーディングを指定しないと正しくデコードされません。

エンコーディングを指定した上でReadStringメソッドを使ってストリームから文字列を読み込む
using System;
using System.IO;
using System.Text;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      // 文字列をデコードする際にShift_JISを使用するように指定してBinaryReaderを作成
      using (BinaryReader reader = new BinaryReader(stream, Encoding.GetEncoding("Shift_JIS"))) {
        // ストリームから文字列を読み込み表示する
        string s = reader.ReadString();

        Console.WriteLine(s);
      }
    }
  }
}
エンコーディングを指定した上でReadStringメソッドを使ってストリームから文字列を読み込む
Imports System
Imports System.IO
Imports System.Text

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      ' 文字列をデコードする際にShift_JISを使用するように指定してBinaryReaderを作成
      Using reader As New BinaryReader(stream, Encoding.GetEncoding("Shift_JIS"))
        ' ストリームから文字列を読み込み表示する
        Dim s As String = reader.ReadString()

        Console.WriteLine(s)
      End Using
    End Using
  End Sub
End Class

文字配列の読み込み (ReadChars)

ストリームから複数バイトを読み込み文字配列(char[])として取得するにはReadCharsメソッドを使うことができます。 このメソッドはReadBytesメソッドと似ていますが、引数にはバイト数ではなく読み込む文字数を指定します。 したがって、実際に読み込まれるバイト数はBinaryReaderが使用するエンコーディングによって変わります。

ReadCharsメソッドを使ってストリームから指定した文字数を読み込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // ストリームから4文字読み込みchar配列として取得する
        char[] chars = reader.ReadChars(4);

        // 読み込んだchar配列を文字列に変換して表示
        string s = new string(chars);

        Console.WriteLine(s);
      }
    }
  }
}

ReadCharsメソッドを使ってストリームから指定した文字数を読み込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' ストリームから4文字読み込みchar配列として取得する
        Dim chars() As Char = reader.ReadChars(4)

        ' 読み込んだchar配列を文字列に変換して表示
        Dim s As New String(chars)

        Console.WriteLine(s)
      End Using
    End Using
  End Sub
End Class

ReadCharsメソッドを使うことでストリームから固定長の文字列フィールドを読み込むことができます。 固定長の文字列フィールドを読み書きする具体例はBinaryWriter.Writeメソッドの解説をご覧ください。

ReadStringメソッドの場合と同様、ReadCharメソッドはBinaryReaderのコンストラクタで指定したエンコーディングでのデコードを行います。 デフォルトではUTF-8でエンコードされているものとして読み込まれます。

文字単位での読み込み・先読み (Read, PeekChar)

Readメソッドを引数なしで呼び出すと、ストリームから1文字(char)を読み込みます。 ReadCharメソッドと似ていますが、戻り値はint/Integerで、ストリームの末尾に達した場合は-1が返されます。 そのため、ストリームの末尾に達した状態でこのメソッドを呼び出しても例外EndOfStreamExceptionはスローされません。

Readメソッドを使ってストリームから一文字ずつ読み込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // 無限ループ
        for (;;) {
          // 一文字読み込む
          int ch = reader.Read();

          if (ch == -1)
            // ストリームの末尾(EOF)に達したので読み込みを終了する
            break;

          // 読み込んだ値をcharにキャストして表示する
          char c = (char)ch;

         Console.Write(c);
        }
      }
    }
  }
}
Readメソッドを使ってストリームから一文字ずつ読み込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' 無限ループ
        Do
          ' 一文字読み込む
          Dim ch As Integer = reader.Read()

          If ch = -1 Then
            ' ストリームの末尾(EOF)に達したので読み込みを終了する
            Exit Do
          End If

          ' 読み込んだ値をCharに変換して表示する
          Dim c As Char = ChrW(ch)

          Console.Write(c)
        Loop
      End Using
    End Using
  End Sub
End Class

ReadStringメソッドの場合と同様、ReadメソッドはBinaryReaderのコンストラクタで指定したエンコーディングでのデコードを行います。 デフォルトではUTF-8でエンコードされているものとして読み込まれます。

PeekCharメソッドを使うと、次の1文字を先読みすることができます。 Peekメソッドの戻り値は引数なしのReadメソッドと同様です。

シーク

BinaryReaderにはストリームのシークを行うメソッドは用意されていません。 シークを行うには、まずBaseStreamプロパティを参照してベースとなっているStreamを取得し、そして取得したStreamのSeekメソッドを呼び出すようにします。

BaseStreamを参照してBinaryReaderをシークする
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // データブロックのサイズを読み込む
        int blockSize = reader.ReadInt32();

        // シークしてデータブロックを読み飛ばす
        reader.BaseStream.Seek(blockSize, SeekOrigin.Current);

        // 次のデータを読み込む
        int data = reader.ReadInt32();
      }
    }
  }
}
BaseStreamを参照してBinaryReaderをシークする
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' データブロックのサイズを読み込む
        Dim blockSize As Integer = reader.ReadInt32()

        ' シークしてデータブロックを読み飛ばす
        reader.BaseStream.Seek(blockSize, SeekOrigin.Current)

        ' 次のデータを読み込む
        Dim data As Integer = reader.ReadInt32()
      End Using
    End Using
  End Sub
End Class

BinaryReaderでは内部でデータの先読みとバッファリングが行われるようになっているため、BaseStreamを参照してストリームのシークを行うと読み込まれる内容に不整合が起こる可能性が考えられます。 実際にそういったことが起こるかどうかはドキュメントには明記されていないため不明確ですが、BinaryReaderにSeekメソッドが用意されていない点を勘案すると、BinaryReaderでランダムアクセスを行うのは避けたほうがよいと思われます。

データを読み飛ばす目的でシークを行いたい場合には、シークを行うかわりにReadBytes等のメソッドを使ってシークしたい分だけデータを読み込み、その戻り値は単に破棄する、といった方法をとることができます。 この方法の場合、ストリーム後方へのシークのみしかできないものの、操作によってバッファリングされている内容に不整合が起きることはないため、確実な方法と言えます。

ReadBytesメソッドでデータを読み捨てることでストリーム後方へのシークを行う
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // データブロックのサイズを読み込む
        int blockSize = reader.ReadInt32();

        // データブロックを読み捨てる
        reader.ReadBytes(blockSize);

        // 次のデータを読み込む
        int data = reader.ReadInt32();
      }
    }
  }
}
ReadBytesメソッドでデータを読み捨てることでストリーム後方へのシークを行う
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' データブロックのサイズを読み込む
        DIm blockSize As Integer = reader.ReadInt32()

        ' データブロックを読み捨てる
        reader.ReadBytes(blockSize)

        ' 次のデータを読み込む
        Dim data As Integer = reader.ReadInt32()
      End Using
    End Using
  End Sub
End Class

BinaryWriter

基本型の書き込み

BinaryWriterで書き込みを行う場合はWriteメソッドを使います。 このメソッドでは与えられた引数の型に従って内容が書き込まれます。 Writeメソッドで基本型の書き込みを行う際、数値リテラルを直接指定して書き込む場合は、リテラルにサフィックスをつけたり明示的にキャストすることにより、書き込もうとしている値の型を明確にすることをおすすめします。

Writeメソッドで基本型の値をストリームに書き込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenWrite("sample.dat")) {
      using (BinaryWriter writer = new BinaryWriter(stream)) {
        // 整数型の値の書き込み
        writer.Write((int)16);

        writer.Write((uint)42);

        writer.Write((byte)72);

        // 実数型の値の読み込み
        writer.Write(0.05f); // float

        writer.Write(Math.PI); // double

        // bool型の値の読み込み
        writer.Write(true);
      }
    }
  }
}
Writeメソッドで基本型の値をストリームに書き込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenWrite("sample.dat")
      Using writer As New BinaryWriter(stream)
        ' 整数型の値の書き込み
        writer.Write(CInt(16))

        writer.Write(CUInt(42))

        writer.Write(CByte(72))

        ' 実数型の値の読み込み
        writer.Write(0.05f) ' Single

        writer.Write(Math.PI) ' Double

        ' Boolean型の値の読み込み
        writer.Write(True)
      End Using
    End Using
  End Sub
End Class

Writeメソッドで書き込める基本型の種類はReadメソッドと同じです。 WriteメソッドではDateTimeやDateTimeOffsetを直接書き込むことはできないので、ToBinary/FromBinaryなどのメソッドを使って数値などに変換してから書き込む必要があります。

バイト配列の書き込み

Writeメソッドではバイト配列の書き込みにも対応しています。 引数に指定する値と意味はStream.Writeメソッドと同じです。 BinaryWriter.Writeメソッドでは、バイト配列のみを指定することでその内容すべてを書き込むようにすることもできます。

Writeメソッドを使ってバイト配列の内容をストリームに書き込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenWrite("sample.dat")) {
      using (BinaryWriter writer = new BinaryWriter(stream)) {
        // 書き込むデータが格納されているバイト配列
        byte[] data = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};

        // dataの0番目から4バイト分(buffer[0]〜buffer[3])を書き込む
        writer.Write(data, 0, 4);

        // dataの4番目から4バイト分(buffer[4]〜buffer[7])を書き込む
        writer.Write(data, 4, 4);

        // data[0]〜data[7]のすべてを書き込む
        writer.Write(data);
      }
    }
  }
}
Writeメソッドを使ってバイト配列の内容をストリームに書き込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenWrite("sample.dat")
      Using writer As New BinaryWriter(stream)
        ' 書き込むデータが格納されているバイト配列
        Dim data() As Byte = New Byte(7) {&H41, &H42, &H43, &H44, &H45, &H46, &H47, &H48}

        ' dataの0番目から4バイト分(buffer(0)〜buffer(3))を書き込む
        writer.Write(data, 0, 4)

        ' dataの4番目から4バイト分(buffer(4)〜buffer(7))を書き込む
        writer.Write(data, 4, 4)

        ' data(0)〜data(7)のすべてを書き込む
        writer.Write(data)
      End Using
    End Using
  End Sub
End Class

なお、書き込み先がファイルに限定される場合はFileクラスのメソッドを使うこともできます。 File.WriteAllBytesメソッドを使うと、BinaryWriterを使わずにバイト配列をファイルに書き込むことができます。

File.WriteAllBytesメソッドを使ってバイト配列の内容をファイルとして書き込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // 書き込むデータが格納されているバイト配列
    byte[] data = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};

    // バイト配列の内容をファイルsample.datに書き込む
    File.WriteAllBytes("sample.dat", data);
  }
}
File.WriteAllBytesメソッドを使ってバイト配列の内容をファイルとして書き込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 書き込むデータが格納されているバイト配列
    Dim data() As Byte = New Byte(7) {&H41, &H42, &H43, &H44, &H45, &H46, &H47, &H48}

    ' バイト配列の内容をファイルsample.datに書き込む
    File.WriteAllBytes("sample.dat", data)
  End Sub
End Class

文字列の書き込み

Writeメソッドは文字列の書き込みにも対応しています。

Writeメソッドを使ってストリームに文字列を書き込む
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (MemoryStream stream = new MemoryStream()) {
      using (BinaryWriter writer = new BinaryWriter(stream)) {
        // Writeメソッドで文字列を書き込む
        writer.Write("ABCDEFGH");
      }

      // 実際に書き込まれた内容を表示する
      Console.WriteLine(BitConverter.ToString(stream.ToArray()));
    }
  }
}
Writeメソッドを使ってストリームに文字列を書き込む
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As new MemoryStream()
      Using writer As New BinaryWriter(stream)
        ' Writeメソッドで文字列を書き込む
        writer.Write("ABCDEFGH")
      End Using

      ' 実際に書き込まれた内容を表示する
      Console.WriteLine(BitConverter.ToString(stream.ToArray()))
    End Using
  End Sub
End Class
実行結果
08-41-42-43-44-45-46-47-48

BinaryWriterで文字列を書き込む場合、まず文字列の長さがストリームに書き込まれ、続けて文字列のバイト表現が書き込まれます。 これにより、読み込みの際に文字列の具体的な長さを知らなくても任意の長さの文字列を読み込むことができるようになっています。 実際、文字列を読み込むBinaryReader.ReadStringメソッドには引数で読み込む文字列の長さを指定することはできません。

上記の実行結果における1バイト目の 0x08 は後続する文字列のバイト数が 8 であることを表しています。 BinaryReader.ReadStringメソッドのドキュメントによると、文字列とその長さは次のように書き込まれます。

現在のストリームから 1 つの文字列を読み取ります。ストリームの文字列は、7 ビットごとにエンコードされた文字列の長さが先頭に付加されています。

BinaryReader.ReadString メソッド ()

Writeメソッドで文字列を書き込む際、デフォルトではUTF-8にエンコードされて書き込まれますが、BinaryWriterのコンストラクタで目的のEncodingを指定することで任意のエンコーディングで文字列を書き込むことができます。

エンコーディングを指定した上でWriteメソッドを使ってストリームに文字列を書き込む
using System;
using System.IO;
using System.Text;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenWrite("sample.dat")) {
      // 文字列をエンコードする際にShift_JISを使用するように指定してBinaryWriterを作成
      using (BinaryWriter writer = new BinaryWriter(stream, Encoding.GetEncoding("Shift_JIS"))) {
        // Writeメソッドで文字列を書き込む
        writer.Write("あいう日本語");
      }
    }
  }
}
エンコーディングを指定した上でWriteメソッドを使ってストリームに文字列を書き込む
Imports System
Imports System.IO
Imports System.Text

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenWrite("sample.dat")
      ' 文字列をエンコードする際にShift_JISを使用するように指定してBinaryWriterを作成
      Using writer As New BinaryWriter(stream, Encoding.GetEncoding("Shift_JIS"))
        ' Writeメソッドで文字列を書き込む
        writer.Write("あいう日本語")
      End Using
    End Using
  End Sub
End Class

文字配列の書き込み

Writeメソッドに文字配列(char[])を指定して書き込みを行う場合は、文字列を書き込む場合とは異なり先頭に文字列の長さは書き込まれません。 そのため、ReadCharsメソッドと組み合わせて使うことで固定長の文字列フィールドの読み書きができるようになります。

以下の例では、ストリームの先頭に全フィールド数、続けて長さ8の固定長文字列フィールドを複数書き込み、それをBinaryReaderで読み込んでいます。

Writeメソッド・ReadCharsメソッドを使って固定長の文字列フィールドを読み書きする
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイルに固定長文字列データを複数書き込む
    using (Stream stream = File.OpenWrite("sample.dat")) {
      using (BinaryWriter writer = new BinaryWriter(stream)) {
        // 書き込む文字列
        string[] fields = new string[] {
          "0123456789",
          "あいう日本語",
          "Hello, world!",
        };

        // 全フィールド数を書き込む
        writer.Write(fields.Length);

        // 長さ8の固定長文字列フィールドを書き込む
        foreach (string field in fields) {
          // 書き込む文字列の長さがちょうど8となるように加工する
          string s = field;

          if (8 < field.Length)
            // 8より長い場合は、8文字に切り詰める
            s = field.Substring(0, 8);
          else if (field.Length < 8)
            // 8より短い場合は、8文字になるまでヌル文字で埋める
            s = field.PadRight(8, '\0');

          // 文字列をchar[]に変換して書き込む
          writer.Write(s.ToCharArray());
        }
      }
    }

    // ファイルから固定長文字列データを複数読み込む
    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // 全フィールド数を読み込む
        int fieldCount = reader.ReadInt32();

        for (int i = 0; i < fieldCount; i++) {
          // 長さ8の固定長文字列フィールドを読み込み、文字列に変換する
          string s = new string(reader.ReadChars(8));

          // ヌル文字で埋められている部分を削る
          s = s.TrimEnd('\0');

          // 得られた文字列を表示
          Console.WriteLine("'{0}'", s);
        }
      }
    }
  }
}
Writeメソッド・ReadCharsメソッドを使って固定長の文字列フィールドを読み書きする
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' ファイルに固定長文字列データを複数書き込む
    Using stream As Stream = File.OpenWrite("sample.dat")
      Using writer As New BinaryWriter(stream)
        ' 書き込む文字列
        Dim fields() As String = New String() { _
          "0123456789", _
          "あいう日本語", _
          "Hello, world!" _
        }

        ' 全フィールド数を書き込む
        writer.Write(fields.Length)

        ' 長さ8の固定長文字列フィールドを書き込む
        For Each field As String In fields
          ' 書き込む文字列の長さがちょうど8となるように加工する
          Dim s As String = field

          If 8 < field.Length Then
            ' 8より長い場合は、8文字に切り詰める
            s = field.Substring(0, 8)
          ElseIf field.Length < 8 Then
            ' 8より短い場合は、8文字になるまでヌル文字で埋める
            s = field.PadRight(8, ChrW(0))
          End If

          ' 文字列をChar()に変換して書き込む
          writer.Write(s.ToCharArray())
        Next
      End Using
    End Using

    ' ファイルから固定長文字列データを複数読み込む
    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' 全フィールド数を読み込む
        Dim fieldCount As Integer = reader.ReadInt32()

        For i As Integer = 0 To fieldCount - 1
          ' 長さ8の固定長文字列フィールドを読み込み、文字列に変換する
          Dim s As New String(reader.ReadChars(8))

          ' ヌル文字で埋められている部分を削る
          s = s.TrimEnd(ChrW(0))

          ' 得られた文字列を表示
          Console.WriteLine("'{0}'", s)
        Next
      End Using
    End Using
  End Sub
End Class
実行結果
'01234567'
'あいう日本語'
'Hello, w'

文字列を書き込む場合と同様、文字配列を書き込む際にはBinaryWriterのコンストラクタで指定したエンコーディングでエンコードされます。 デフォルトではUTF-8でエンコードされた上で書き込まれます。

シーク

BinaryWriterの書き込み位置を変更するにはSeekメソッドを使うことができます。 また、BaseStreamプロパティを参照してベースとなっているStreamを取得し、そして取得したStreamのSeekメソッドを呼び出すことでもシークを行うことができます。

Seekメソッドを使ってストリームをシークする
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenWrite("sample.dat")) {
      using (BinaryWriter writer = new BinaryWriter(stream)) {
        // データを書き込む
        writer.Write((int)16);

        // ストリームの先頭にシーク
        writer.Seek(0, SeekOrigin.Begin);
        //writer.BaseStream.Seek(0, SeekOrigin.Begin); // このようにしても同じ

        // 別のデータを上書きする
        writer.Write((int)42);
      }
    }
  }
}
Seekメソッドを使ってストリームをシークする
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenWrite("sample.dat")
      Using writer As New BinaryWriter(stream)
        ' データを書き込む
        writer.Write(CInt(16))

        ' ストリームの先頭にシーク
        writer.Seek(0, SeekOrigin.Begin)
        'writer.BaseStream.Seek(0, SeekOrigin.Begin) ' このようにしても同じ

        ' 別のデータを上書きする
        writer.Write(CInt(42))
      End Using
    End Using
  End Sub
End Class

BinaryWriterは書き込み内容のバッファリングは行われないため、BinaryReaderのシークの場合とは異なりシークを行なっても書き込まれる内容に不整合が起こることはありません。

構造体・クラスの読み書き

BinaryReader・BinaryWriterには任意の構造体・クラスを読み書きするメソッドは用意されていません。 そのため、BinaryReader・BinaryWriterで構造体やクラスを扱う場合は、以下のようにフィールドをひとつずつ読み書きする必要があります。

BinaryReader/BinaryWriterを用いて構造体の読み書きを行う
using System;
using System.IO;

// ファイルへの読み書きで使用する構造体
struct Date {
  public short Year;
  public byte Month;
  public byte Day;
}

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenWrite("sample.dat")) {
      using (BinaryWriter writer = new BinaryWriter(stream)) {
        // 構造体を作成し、値を設定
        Date d = new Date();

        d.Year = 2013;
        d.Month = 4;
        d.Day = 1;

        // 構造体のフィールドを1つずつ書き込む
        writer.Write(d.Year);
        writer.Write(d.Month);
        writer.Write(d.Day);
      }
    }

    using (Stream stream = File.OpenRead("sample.dat")) {
      using (BinaryReader reader = new BinaryReader(stream)) {
        // 構造体を作成し、値を読み込む
        Date d = new Date();

        d.Year = reader.ReadInt16();
        d.Month = reader.ReadByte();
        d.Day = reader.ReadByte();

        // 構造体に読み込んだ内容を表示
        Console.WriteLine("{0}-{1}-{2}", d.Year, d.Month, d.Day);
      }
    }
  }
}
BinaryReader/BinaryWriterを用いて構造体の読み書きを行う
Imports System
Imports System.IO

' ファイルへの読み書きで使用する構造体
Structure MyDate
  Public Year As Short
  Public Month As Byte
  Public Day As Byte
End Structure

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenWrite("sample.dat")
      Using writer As New BinaryWriter(stream)
        ' 構造体を作成し、値を設定
        Dim d As New MyDate

        d.Year = 2013
        d.Month = 4
        d.Day = 1

        ' 構造体のフィールドを1つずつ書き込む
        writer.Write(d.Year)
        writer.Write(d.Month)
        writer.Write(d.Day)
      End Using
    End Using

    Using stream As Stream = File.OpenRead("sample.dat")
      Using reader As New BinaryReader(stream)
        ' 構造体を作成し、値を読み込む
        Dim d As MyDate

        d.Year = reader.ReadInt16()
        d.Month = reader.ReadByte()
        d.Day = reader.ReadByte()

        ' 構造体に読み込んだ内容を表示
        Console.WriteLine("{0}-{1}-{2}", d.Year, d.Month, d.Day)
      End Using
    End Using
  End Sub
End Class

ストリーム中のブロックを構造体として読み書きしたい場合、fread/fwrite関数のような読み書きを行いたい場合は、いったんバイト配列として読み書きし、さらにバイト配列から構造体に変換する必要があります。 具体的な実装例についてはBinaryReader・BinaryWriterでの構造体の読み書きで紹介しています。

ベースとなるストリームのクローズ

BinaryReader・BinaryWriterでは、Closeメソッドを呼び出したりusingステートメントから抜けた際にはベースとなったストリームも閉じられ、ストリームに対するアクセスができなくなります。 BinaryReader・BinaryWriterを閉じたあとにベースとなったストリームに対して操作を行おうとすると例外ObjectDisposedExceptionがスローされます。 これは、StreamReader・StreamWriterでも同様です。 BinaryReader・BinaryWriterを使用したあとも引き続きストリームを使う方法についてはStreamReader・StreamWriterの解説を参照してください。

.NET Framework 4.5からは、コンストラクタの引数leaveOpenにtrueを指定すると、BinaryReader・BinaryWriterを閉じてもベースとなるストリームを開いたままにすることができます。 ただし、同時に引数encodingも省略せずに指定する必要があります。

コンストラクタの引数leaveOpenを指定してBinaryReader・BinaryWriterを閉じてもベースとなるストリームを開いたままにする
using System;
using System.IO;
using System.Text;

class Sample {
  static void Main()
  {
    using (Stream stream = File.OpenRead("sample.dat")) {
      // leaveOpenにtrueを指定してBinaryReaderを作成
      const bool leaveOpen = true;

      using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen)) {
        reader.ReadByte();
      }

      // Usingステートメントから抜けてreaderが閉じられても
      // streamは閉じられないのでObjectDisposedExceptionはスローされない
      stream.ReadByte();
    }

    using (Stream stream = File.OpenWrite("sample.dat")) {
      // leaveOpenにtrueを指定してBinaryWriterを作成
      const bool leaveOpen = true;

      using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen)) {
        writer.Write(16);
      }

      // BinaryWriterの場合も同様で、ObjectDisposedExceptionはスローされない
      stream.WriteByte(0);
    }
  }
}
コンストラクタの引数leaveOpenを指定してBinaryReader・BinaryWriterを閉じてもベースとなるストリームを開いたままにする
Imports System
Imports System.IO
Imports System.Text

Class Sample
  Shared Sub Main()
    Using stream As Stream = File.OpenRead("sample.dat")
      ' leaveOpenにTrueを指定してBinaryReaderを作成
      Const leaveOpen As Boolean = True

      Using reader As New BinaryReader(stream, Encoding.UTF8, leaveOpen)
        reader.ReadByte()
      End Using

      ' usingステートメントから抜けてreaderが閉じられても
      ' streamは閉じられないのでObjectDisposedExceptionはスローされない
      stream.ReadByte()
    End Using

    Using stream As Stream = File.OpenWrite("sample.dat")
      ' leaveOpenにTrueを指定してStreamWriterを作成
      Dim leaveOpen As Boolean = True

      Using writer As New BinaryWriter(stream, Encoding.UTF8, leaveOpen)
        writer.Write(16)
      End Using

      ' BinaryWriterの場合も同様で、ObjectDisposedExceptionはスローされない
      stream.WriteByte(0)
    End Using
  End Sub
End Class