C#でMemoryStreamとStreamWriterを組み合わせて使ったら遭遇した想定外の仕様を注意点と対処として記録します。
- StreamWriterはFlushを呼び出すまでMemoryStreamへ書き込まれない
- StreamWriterのCloseメソッドは関連付けたMemoryStreamのCloseメソッドを呼び出す
- MemoryStream.GetBufferメソッドは自動拡張されたバイト配列を返す
StreamWriterはFlushを呼び出すまでMemoryStreamへ書き込まれない
StreamWriterはFlushメソッドまでMemoryStreamへ書き込まれません。したがって、次のコードは空のバイト配列を返します。例外は発生しません。
失敗するコード
using var、トップレベルステートメント(C# 9.0)
using System.IO; var bytes = GetStreamBufferOfString("TEST"); static byte[] GetStreamBufferOfString(string s) { using var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write(s); return stream.GetBuffer(); }
using {...}、Main関数
using System.IO; class Program { static void Main() { var bytes = GetStreamBufferOfString("TEST"); // byte[0] {} } static byte[] GetStreamBufferOfString(string s) { using (var stream = new MemoryStream()) { var writer = new StreamWriter(stream); writer.Write(s); return stream.GetBuffer(); } } }
対策したコード
GetBufferメソッドの前にFlushメソッドを呼び出すことでStreamWriterの書き込み結果を反映することができます。
using var、トップレベルステートメント(C# 9.0)
using System; using System.IO; var bytes = GetStreamBufferOfString("TEST"); // byte[256] {84, 69, 83, 84, 0, ..., 0} Console.ReadKey(); static byte[] GetStreamBufferOfString(string s) { using var stream = new MemoryStream(); { var writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); return stream.GetBuffer(); } }
using {...}、Main関数
using System.IO; class Program { static void Main() { var bytes = GetStreamBufferOfString("TEST"); // byte[256] {84, 69, 83, 84, 0, ..., 0} } static byte[] GetStreamBufferOfString(string s) { using (var stream = new MemoryStream()) { var writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); return stream.GetBuffer(); } } }
StreamWriterのCloseメソッドは関連付けたMemoryStreamのCloseメソッドを呼び出す
StreamWriterのCloseメソッドは関連付けたMemoryStreamのCloseメソッドを呼び出します。メモリリークを意識してStreamWriterにusingステートメントを使用した場合、これが原因で例外が発生する場合があります。
例えば次のコードは内側のusingステートメントの終了時にStreamWriterがCloseされるため、外側のusingステートメントにおけるMemoryStreamへのアクセスは例外を発生させます。
using var、トップレベルステートメント(C# 9.0)
using System.IO; using var stream = new MemoryStream(); using var writer = new StreamWriter(stream); // ここに適当な処理が記述されます。 writer.Dispose(); var position = stream.Position; // System.ObjectDisposedException: 'Cannot access a closed Stream.'
using {...}、Main関数
using System.IO; class Program { static void Main() { using (var stream = new MemoryStream()) { using (var writer = new StreamWriter(stream)) { // ここに適当な処理が記述されます。 } var position = stream.Position; // System.ObjectDisposedException: 'Cannot access a closed Stream.' } } }
MemoryStream.GetBufferメソッドは自動拡張されたバイト配列を返す
MemoryStream.GetBufferメソッドの返すバイト配列は自動拡張されたバイト配列そのものです。次のコードは想定外に長いバイト配列を取得します。
using var、トップレベルステートメント(C# 9.0)
using System.IO; class Program { static void Main() { var bytes = GetStreamBufferOfString("TEST"); // byte[256] {84, 69, 83, 84, 0, ..., 0} } static byte[] GetStreamBufferOfString(string s) { using var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); return stream.GetBuffer(); } }
using {...}、Main関数
using System.IO; class Program { static void Main() { var bytes = GetStreamBufferOfString("TEST"); // byte[256] {84, 69, 83, 84, 0, ..., 0} } static byte[] GetStreamBufferOfString(string s) { using (var stream = new MemoryStream()) { var writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); return stream.GetBuffer(); } } }
LINQのTakeやAsSpanとMemoryStream.Positionプロパティを組み合わせることで余分を含まない書き込み内容を取得することができます。
using var、トップレベルステートメント(C# 9.0)
using System; using System.IO; using System.Linq; var bytes = GetStreamBuffer(); // byte[4] {84, 69, 83, 84} Console.ReadKey(); static byte[] GetStreamBuffer() { using var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write("TEST"); writer.Flush(); return stream.GetBuffer().Take((int)stream.Position).ToArray(); }
using {...}、Main関数
using System; using System.IO; using System.Linq; class Program { static void Main() { var bytes = GetStreamBuffer(); // byte[4] {84, 69, 83, 84} } private static byte[] GetStreamBuffer() { using (var stream = new MemoryStream()) { var writer = new StreamWriter(stream); writer.Write("TEST"); writer.Flush(); return stream.GetBuffer().Take((int)stream.Position).ToArray(); } } }