potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# 8-9 MemoryStreamとStreamWriterを組み合わせるときの注意点と対処

C#でMemoryStreamとStreamWriterを組み合わせて使ったら遭遇した想定外の仕様を注意点と対処として記録します。

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();
        }
    }
}