MemoryStreamクラスはメモリ上に確保されたバイト配列に対して読み書きを行うためのStreamです。 Streamクラスから派生したクラスであるため、ほとんどの機能と操作方法はStreamクラスと同じであることから、その部分については省略します。 MemoryStreamを使った読み込み・書き込み等の操作についてはStreamクラスStreamReaderクラス・StreamWriterクラスBinaryReaderクラス・BinaryWriterクラスを参照してください。 ここではMemoryStreamクラスに特有な箇所についてのみ解説します。

インスタンスの作成

MemoryStreamでは、読み書きに使用するバッファ(バイト配列)をMemoryStream自身に作成させるか、それとも別に存在する既存のバイト配列を使用するかを指定することができます。

MemoryStream自身にバッファを作成させるようにした場合、MemoryStreamに書き込む内容のサイズに応じて自動的にバッファサイズが拡張されます(可変長のバッファ)。 この場合、SetLengthメソッドでストリームの長さ(サイズ)を変更することも可能となります。

一方、既存のバイト配列を使うように指定した場合、MemoryStreamでは指定されたバイト配列への読み書きのみを行います。 指定されたバイト配列のサイズを超えて書き込もうとした場合や、SetLengthメソッドで長さを変更しようとした場合には例外NotSupportedExceptionがスローされます。 また、既存のバイト配列を指定する場合は、バイト配列の一部分のみを参照できるようにしたり、書き込みを禁止して読み込み専用にすることもできます。

使用するバッファや書き込みの許可などの指定は、次のようにMemoryStreamのインスタンスを作成する際にコンストラクタで指定します。

MemoryStreamを作成する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // 書き込む内容に応じて自動的に拡張されるバッファを使用するMemoryStreamを作成
    using (MemoryStream stream = new MemoryStream()) {
      // BinaryWriterを使ってデータを書き込む
      BinaryWriter writer = new BinaryWriter(stream);

      writer.Write(0x12345678);

      // MemoryStreamに書き込んだ内容をバイト配列として取得して表示
      byte[] array = stream.ToArray();

      Console.WriteLine(BitConverter.ToString(array));
    }

    // 既存のバイト配列からデータを参照する読み込み専用のMemoryStreamを作成
    byte[] data = new byte[] {0x78, 0x56, 0x34, 0x12};

    using (MemoryStream stream = new MemoryStream(data, false)) {
      // BinaryReaderを使ってデータを読み込む
      BinaryReader reader = new BinaryReader(stream);

      Console.WriteLine("{0:x8}", reader.ReadInt32());
    }
  }
}
MemoryStreamを作成する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 書き込む内容に応じて自動的に拡張されるバッファを使用するMemoryStreamを作成
    Using stream As New MemoryStream()
      ' BinaryWriterを使ってデータを書き込む
      Dim writer As New BinaryWriter(stream)

      writer.Write(&h12345678)

      ' MemoryStreamに書き込んだ内容をバイト配列として取得して表示
      Dim array() As Byte = stream.ToArray()

      Console.WriteLine(BitConverter.ToString(array))
    End Using

    ' 既存のバイト配列からデータを参照する読み込み専用のMemoryStreamを作成
    Dim data() As Byte = New Byte(3) {&h78, &h56, &h34, &h12}

    Using stream As New MemoryStream(data, False)
      ' BinaryReaderを使ってデータを読み込む
      Dim reader As New BinaryReader(stream)

      Console.WriteLine("{0:x8}", reader.ReadInt32())
    End Using
  End Sub
End Class

MemoryStreamのコンストラクタにはいくつかの種類があります。 コンストラクタと作成されるMemoryStreamの動作・ケーパビリティをまとめると次のようになります。

MemoryStreamのコンストラクタとインスタンスの動作・ケーパビリティ
コンストラクタのオーバーロード コンストラクタの動作と作成されるMemoryStream 書き込み サイズの変更
new MemoryStream() 可変長のバッファを使用して読み書きを行う
new MemoryStream(capacity) 可変長のバッファを使用して読み書きを行う
バッファの初期サイズをcapacityで指定する
new MemoryStream(buffer) 既存のバイト配列bufferに対して読み書きを行う 不可
new MemoryStream(buffer, writable) 既存のバイト配列bufferに対して読み書きを行う
bufferへの書き込みを許可するかどうかをwritableで指定する
writableの指定による 不可
new MemoryStream(buffer, index, count) 既存のバイト配列bufferの[index]〜[index + count - 1]の範囲のみに対して読み書きを行う 不可
new MemoryStream(buffer, index, count, writable) 既存のバイト配列bufferの[index]〜[index + count - 1]の範囲のみに対して読み書きを行う
bufferへの書き込みを許可するかどうかをwritableで指定する
writableの指定による 不可

現在確保されているバッファのサイズを取得するにはCapacityプロパティを参照します。 この値は、現在のストリームの長さ(Length)とはことなるものです。

バイト配列への変換 (ToArray)

MemoryStreamに書き込んだ内容をバイト配列に変換して取得するには、ToArrayメソッドを呼び出します。

ToArrayメソッドを使ってMemoryStreamの内容をバイト配列に変換する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (MemoryStream stream = new MemoryStream()) {
      // BinaryWriterを使ってデータを書き込む
      BinaryWriter writer = new BinaryWriter(stream);

      writer.Write((uint)0x01234567);
      writer.Write((ushort)0x89AB);
      writer.Write((byte)0xCD);
      writer.Write((byte)0xEF);

      // MemoryStreamに書き込んだ内容をバイト配列として取得して表示
      byte[] array = stream.ToArray();

      Console.WriteLine("Length = {0}", array.Length);
      Console.WriteLine(BitConverter.ToString(array));
    }
  }
}
ToArrayメソッドを使ってMemoryStreamの内容をバイト配列に変換する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As New MemoryStream()
      ' BinaryWriterを使ってデータを書き込む
      Dim writer As New BinaryWriter(stream)

      writer.Write(CInt(&h01234567))
      writer.Write(CUshort(&h89AB))
      writer.Write(CByte(&hCD))
      writer.Write(CByte(&hEF))

      ' MemoryStreamに書き込んだ内容をバイト配列として取得して表示
      Dim array() As Byte = stream.ToArray()

      Console.WriteLine("Length = {0}", array.Length)
      Console.WriteLine(BitConverter.ToString(array))
    End Using
  End Sub
End Class
実行結果
Length = 8
67-45-23-01-AB-89-CD-EF

ToArrayメソッドは、常にMemoryStreamの内容を新しく作成した配列にコピーして返します。 既存のバイト配列を指定してMemoryStreamを作成した場合、ToArrayメソッドではその配列とは別のインスタンスが返されます。 また、既存のバイト配列の一部を指定したMemoryStreamでは、その範囲・長さの配列が返されます。

既存の配列からMemoryStreamを作成した場合におけるToArrayメソッドが返す配列インスタンス
using System;
using System.IO;

class Sample {
  static void Main()
  {
    byte[] data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};

    // dataを使ってMemoryStreamを作成
    using (MemoryStream stream = new MemoryStream(data)) {
      // streamをバイト配列に変換
      byte[] array = stream.ToArray();

      Console.WriteLine("data  : {0}", BitConverter.ToString(data));
      Console.WriteLine("array : {0}", BitConverter.ToString(array));
      Console.WriteLine("data == array : {0}", data == array);
      Console.WriteLine();
    }

    // dataの2バイト目から4バイト分を使ってMemoryStreamを作成
    using (MemoryStream stream = new MemoryStream(data, 2, 4)) {
      // streamをバイト配列に変換
      byte[] array = stream.ToArray();

      Console.WriteLine("data  : {0}", BitConverter.ToString(data));
      Console.WriteLine("array : {0}", BitConverter.ToString(array));
      Console.WriteLine("data == array : {0}", data == array);
      Console.WriteLine();
    }
  }
}
既存の配列からMemoryStreamを作成した場合におけるToArrayメソッドが返す配列インスタンス
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Dim data() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hAB, &hCD, &hEF}

    ' dataを使ってMemoryStreamを作成
    Using stream As New MemoryStream(data)
      ' streamをバイト配列に変換
      Dim array() As Byte = stream.ToArray()

      Console.WriteLine("data  : {0}", BitConverter.ToString(data))
      Console.WriteLine("array : {0}", BitConverter.ToString(array))
      Console.WriteLine("data Is array : {0}", data Is array)
      Console.WriteLine()
    End Using

    ' dataの2バイト目から4バイト分を使ってMemoryStreamを作成
    Using stream As New MemoryStream(data, 2, 4)
      ' streamをバイト配列に変換
      Dim array() As Byte = stream.ToArray()

      Console.WriteLine("data  : {0}", BitConverter.ToString(data))
      Console.WriteLine("array : {0}", BitConverter.ToString(array))
      Console.WriteLine("data Is array : {0}", data Is array)
      Console.WriteLine()
    End Using
  End Sub
End Class
実行結果
data  : 01-23-45-67-89-AB-CD-EF
array : 01-23-45-67-89-AB-CD-EF
data == array : False

data  : 01-23-45-67-89-AB-CD-EF
array : 45-67-89-AB
data == array : False

バッファの取得

GetBufferメソッド

ToArrayメソッドではMemoryStreamのコピーが返されますが、MemoryStreamの内部で使用されるバッファを直接参照する必要がある場合にはGetBufferメソッドを使います。

GetBufferメソッドを使ってMemoryStreamの内部バッファを直接参照する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // 初期バッファサイズに32を指定したMemoryStreamを作成
    using (MemoryStream stream = new MemoryStream(32)) {
      // BinaryWriterを使ってデータを書き込む
      BinaryWriter writer = new BinaryWriter(stream);

      writer.Write((uint)0x01234567);
      writer.Write((ushort)0x89AB);
      writer.Write((byte)0xCD);
      writer.Write((byte)0xEF);

      Console.WriteLine("stream.Length = {0}", stream.Length);
      Console.WriteLine("stream.Capacity = {0}", stream.Capacity);

      // MemoryStreamの内部バッファを取得する
      byte[] buffer = stream.GetBuffer();

      Console.WriteLine("buffer.Length = {0}", buffer.Length);
      Console.WriteLine(BitConverter.ToString(buffer));
    }
  }
}
GetBufferメソッドを使ってMemoryStreamの内部バッファを直接参照する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 初期バッファサイズに32を指定したMemoryStreamを作成
    Using stream As New MemoryStream(32)
      ' BinaryWriterを使ってデータを書き込む
      Dim writer As New BinaryWriter(stream)

      writer.Write(CInt(&h01234567))
      writer.Write(CUshort(&h89AB))
      writer.Write(CByte(&hCD))
      writer.Write(CByte(&hEF))

      Console.WriteLine("stream.Length = {0}", stream.Length)
      Console.WriteLine("stream.Capacity = {0}", stream.Capacity)

      ' MemoryStreamの内部バッファを取得する
      Dim buffer() As Byte = stream.GetBuffer()

      Console.WriteLine("buffer.Length = {0}", buffer.Length)
      Console.WriteLine(BitConverter.ToString(buffer))
    End Using
  End Sub
End Class
実行結果
stream.Length = 8
stream.Capacity = 32
buffer.Length = 32
67-45-23-01-AB-89-CD-EF-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00

MemoryStreamのコンストラクタで既存のバイト配列を指定した場合は、GetBufferメソッドからはそのバイト配列(同じインスタンス)が返されます。 ただし、コンストラクタで引数publiclyVisibleにtrueを指定しないとGetBufferメソッドでバッファを取得することは出来ません。 publiclyVisibleにfalseを指定したMemoryStreamに対してGetBufferメソッドを呼び出した場合、例外UnauthorizedAccessExceptionがスローされます。

GetBufferメソッドによるMemoryStream内部バッファの取得を禁止する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    byte[] data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};

    // publiclyVisibleにtrueを指定してMemoryStreamを作成
    bool publiclyVisible = true;

    Console.WriteLine("publiclyVisible = {0}", publiclyVisible);

    using (MemoryStream stream = new MemoryStream(data, 0, data.Length, false, publiclyVisible)) {
      // 内部バッファを取得
      byte[] buffer = stream.GetBuffer();

      Console.WriteLine("Length = {0}", buffer.Length);
      Console.WriteLine(BitConverter.ToString(buffer));
      Console.WriteLine("buffer == data : {0}", buffer == data);
    }

    Console.WriteLine();

    // publiclyVisibleにfalseを指定してMemoryStreamを作成
    publiclyVisible = false;

    Console.WriteLine("publiclyVisible = {0}", publiclyVisible);

    using (MemoryStream stream = new MemoryStream(data, 0, data.Length, false, publiclyVisible)) {
      // 内部バッファを取得 (UnauthorizedAccessExceptionがスローされる)
      byte[] buffer = stream.GetBuffer();

      Console.WriteLine("Length = {0}", buffer.Length);
      Console.WriteLine(BitConverter.ToString(buffer));
      Console.WriteLine("buffer == data", buffer == data);
    }
  }
}
GetBufferメソッドによるMemoryStream内部バッファの取得を禁止する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Dim data() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hAB, &hCD, &hEF}

    ' publiclyVisibleにtrueを指定してMemoryStreamを作成
    Dim publiclyVisible As Boolean = True

    Console.WriteLine("publiclyVisible = {0}", publiclyVisible)

    Using stream As New MemoryStream(data, 0, data.Length, False, publiclyVisible)
      ' 内部バッファを取得
      Dim buffer() As Byte = stream.GetBuffer()

      Console.WriteLine("Length = {0}", buffer.Length)
      Console.WriteLine(BitConverter.ToString(buffer))
      Console.WriteLine("buffer Is data : {0}", buffer Is data)
    End Using

    Console.WriteLine()

    ' publiclyVisibleにfalseを指定してMemoryStreamを作成
    publiclyVisible = false

    Console.WriteLine("publiclyVisible = {0}", publiclyVisible)

    Using stream As New MemoryStream(data, 0, data.Length, false, publiclyVisible)
      ' 内部バッファを取得 (UnauthorizedAccessExceptionがスローされる)
      Dim buffer() As Byte = stream.GetBuffer()

      Console.WriteLine("Length = {0}", buffer.Length)
      Console.WriteLine(BitConverter.ToString(buffer))
      Console.WriteLine("buffer Is data", buffer Is data)
    End Using
  End Sub
End Class
実行結果
publiclyVisible = True
Length = 8
01-23-45-67-89-AB-CD-EF
buffer Is data : True

publiclyVisible = False

ハンドルされていない例外: System.UnauthorizedAccessException: MemoryStream の内部バッファーにアクセスできません。
   場所 System.IO.MemoryStream.GetBuffer()
   場所 Sample.Main()

GetBufferメソッドは、常にMemoryStreamが使用しているバッファと同一のインスタンスを返します。 従って、GetBufferメソッドで得られるバイト配列の内容を変更すると、MemoryStreamで読み込まれる結果にも影響することになります。

GetBufferメソッドで取得した内部バッファへの変更
using System;
using System.IO;

class Sample {
  static void Main()
  {
    byte[] initialData = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};

    // ストリームへの書き込みは許可しないが、バッファの取得は許可するMemoryStreamを作成
    using (MemoryStream stream = new MemoryStream(initialData, 0, initialData.Length, false, true)) {
      // 内部バッファを取得
      byte[] buffer = stream.GetBuffer();

      // 内部バッファの内容をクリアする
      Array.Clear(buffer, 0, buffer.Length);

      // Readメソッドで読み込む
      byte[] data = new byte[8];

      stream.Read(data, 0, data.Length);

      // 読み込んだ内容を表示する
      Console.WriteLine(BitConverter.ToString(data));
    }
  }
}
GetBufferメソッドで取得した内部バッファへの変更
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Dim initialData() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hAB, &hCD, &hEF}

    ' ストリームへの書き込みは許可しないが、バッファの取得は許可するMemoryStreamを作成
    Using stream As New MemoryStream(initialData, 0, initialData.Length, False, True)
      ' 内部バッファを取得
      Dim buffer() As Byte = stream.GetBuffer()

      ' 内部バッファの内容をクリアする
      Array.Clear(buffer, 0, buffer.Length)

      ' Readメソッドで読み込む
      Dim data(7) As Byte

      stream.Read(data, 0, data.Length)

      ' 読み込んだ内容を表示する
      Console.WriteLine(BitConverter.ToString(data))
    End Using
  End Sub
End Class
実行結果
00-00-00-00-00-00-00-00

TryGetBufferメソッド

.NET Framework 4.6以降ではTryGetBufferメソッドを使うことによっても内部バッファを取得することができます。 TryGetBufferメソッドとGetBufferメソッドの違いは次の二点です。

  • バッファが取得できない場合、TryGetBufferメソッドは例外をスローせず、戻り値としてfalseを返す。
  • 取得したバッファはArraySegment<byte>としてTryGetBufferメソッドのoutパラメータに格納される。

バッファが取得できるかどうかの動作はGetBufferメソッドと同様で、MemoryStreamのコンストラクタで指定した値によって決まります。

TryGetBufferメソッドを使ってMemoryStreamの内部バッファを参照するArraySegmentを取得する .NET Framework 4.6
using System;
using System.IO;
using System.Linq;

class Sample {
  static void Main()
  {
    byte[] data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};

    Console.WriteLine(BitConverter.ToString(data));

    bool publiclyVisible = true;

    // 引数publiclyVisibleにtrueを指定し、dataの一部分を参照するMemoryStreamを作成
    using (MemoryStream stream = new MemoryStream(data, 2, 4, false, publiclyVisible)) {
      // 内部バッファを参照するためのArraySegment
      ArraySegment<byte> buffer;

      // 内部バッファを取得
      if (stream.TryGetBuffer(out buffer)) {
        // 取得したバッファが参照する内容を表示する
        Console.WriteLine("Offset = {0}", buffer.Offset);
        Console.WriteLine("Count = {0}", buffer.Count);

        // BitConverter.ToStringと同じ書式でArraySegmentの内容を文字列化
        Console.WriteLine(string.Join("-", buffer.Select(b => b.ToString("X2"))));
      }
    }
  }
}
TryGetBufferメソッドを使ってMemoryStreamの内部バッファを参照するArraySegmentを取得する .NET Framework 4.6
Imports System
Imports System.IO
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim data() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hAB, &hCD, &hEF}

    Console.WriteLine(BitConverter.ToString(data))

    Dim publiclyVisible As Boolean = True

    ' 引数publiclyVisibleにtrueを指定し、dataの一部分を参照するMemoryStreamを作成
    Using stream As New MemoryStream(data, 2, 4, False, publiclyVisible)
      ' 内部バッファを参照するためのArraySegment
      Dim buffer As ArraySegment(Of Byte)

      ' 内部バッファを取得
      If stream.TryGetBuffer(buffer) Then
        ' 取得したバッファが参照する内容を表示する
        Console.WriteLine("Offset = {0}", buffer.Offset)
        Console.WriteLine("Count = {0}", buffer.Count)

        ' BitConverter.ToStringと同じ書式でArraySegmentの内容を文字列化
        Console.WriteLine(String.Join("-", buffer.Select(Function(b) b.ToString("X2"))))
      End If
    End Using
  End Sub
End Class
実行結果
01-23-45-67-89-AB-CD-EF
Offset = 2
Count = 4
45-67-89-AB

GetBufferメソッドでは、バッファとして使用している配列全体が返されてしまうため、取得したバッファを参照する際に誤ってMemoryStreamが参照していない領域を参照してしまう可能性があります。

一方TryGetBufferメソッドでは取得したバッファはArraySegment<byte>として返されます。 これにより、返されたArraySegmentのArrayプロパティを直接参照しない限りは、MemoryStreamが参照していない領域を誤って参照することは起こりえなくなります。

したがって、配列の一部を参照するようなMemoryStreamでは、バッファを取得する場合はGetBufferメソッドよりもTryGetBufferメソッドを使った方が誤った操作を行う可能性が低いためより安全と言えます。

ArraySegment構造体については部分配列 §.ArraySegment構造体を参照してください。

バッファの破棄・クリア

MemoryStreamにはバッファの内容をクリアするStringBuilder.Clearのようなメソッドは用意されていません。 かわりに、SetLengthメソッドを使ってストリームの長さを0にすることによりMemoryStreamに書き込まれている内容を破棄することができます。

MemoryStreamのバッファの内容をクリアする
using System;
using System.IO;

class Sample {
  static void Main()
  {
    using (MemoryStream stream = new MemoryStream()) {
      // streamに8バイト分書き込む
      for (int i = 0; i < 8; i++) {
        stream.WriteByte((byte)i);
      }

      Console.WriteLine("Length = {0} : {1}", stream.Length, BitConverter.ToString(stream.ToArray()));

      // SetLengthを使ってstreamに書き込んだ内容を破棄
      stream.SetLength(0);

      Console.WriteLine("Length = {0} : {1}", stream.Length, BitConverter.ToString(stream.ToArray()));

      // streamに4バイト分書き込む
      for (int i = 4; i < 8; i++) {
        stream.WriteByte((byte)i);
      }

      Console.WriteLine("Length = {0} : {1}", stream.Length, BitConverter.ToString(stream.ToArray()));
    }
  }
}
MemoryStreamのバッファの内容をクリアする
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    Using stream As New MemoryStream()
      ' streamに8バイト分書き込む
      For i As Integer = 0 To 7
        stream.WriteByte(CByte(i))
      Next

      Console.WriteLine("Length = {0} : {1}", stream.Length, BitConverter.ToString(stream.ToArray()))

      ' SetLengthを使ってstreamに書き込んだ内容を破棄
      stream.SetLength(0)

      Console.WriteLine("Length = {0} : {1}", stream.Length, BitConverter.ToString(stream.ToArray()))

      ' streamに4バイト分書き込む
      For i As Integer = 4 To 7
        stream.WriteByte(CByte(i))
      Next

      Console.WriteLine("Length = {0} : {1}", stream.Length, BitConverter.ToString(stream.ToArray()))
    End Using
  End Sub
End Class
実行結果
Length = 8 : 00-01-02-03-04-05-06-07
Length = 0 : 
Length = 4 : 04-05-06-07