DEV Community

Thomas Eyde
Thomas Eyde

Posted on

Memory stream is not expandable

Every time I have encountered this exception, I have spent too much time figuring out how to solve it.

My usual use case is to simulate an Azure Storage container, where I need to open a stream for reading, writing or appending.

Read and write is simple:

class BlobContainerStub
{
    readonly Dictionary<string, MemoryStream> streams = [];

    public async Task<Stream> OpenRead(string name)
    {
        await Task.CompletedTask;
        var stream = streams.GetValueOrDefault(name);
        if (stream == null) throw new InvalidOperationException();
        return new MemoryStream(stream.ToArray());
    }

    public async Task<Stream> OpenWrite(string name)
    {
        await Task.CompletedTask;
        var stream = new MemoryStream();
        streams[name] = stream;
        return stream;
    }
}
Enter fullscreen mode Exit fullscreen mode

Append is where it becomes difficult. My first, naive approach:

class BlobContainerStub
{
    public async Task<Stream> OpenAppend(string name)
    {
        await Task.CompletedTask;
        var stream = streams.GetValueOrDefault(name);
        if (stream == null)
        {
            stream = new MemoryStream();
            streams[name] = stream;
            return stream;
        }
        else
        {
            // ObjectDisposedException: 
            // Cannot access a closed Stream.
            stream.Seek(0, SeekOrigin.End);
            return stream;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Of course the stream is closed, the previous operation closed it. It is not possible to reopen it, so a new one is needed:

    public async Task<Stream> OpenAppend(string name)
    {
        await Task.CompletedTask;
        var stream = streams.GetValueOrDefault(name);
        if (stream == null)
        {
            stream = new MemoryStream();
            streams[name] = stream;
            return stream;
        }
        else
        {
            // ArgumentException:
            // Stream was not writable.
            streams[name] = new MemoryStream(stream.ToArray());
            return stream;
        }
    }

Enter fullscreen mode Exit fullscreen mode

If you create a MemoryStream this way, it is not writable. I keep forgetting this, because I cannot understand why it is that way.

Next attempt is to copy data from old to new stream:

    public async Task<Stream> OpenAppend(string name)
    {
        var stream = streams.GetValueOrDefault(name);
        if (stream == null)
        {
            stream = new MemoryStream();
            streams[name] = stream;
            return stream;
        }
        else
        {
            // ObjectDisposedException:
            // Cannot access a closed Stream.
            var appendable = new MemoryStream();
            await stream.CopyToAsync(appendable);
            return appendable;
        }
    }
Enter fullscreen mode Exit fullscreen mode

But now I am back to the stream being closed. How about writing the content to the new stream?

class BlobContainerStub
{
    public async Task<Stream> OpenAppend(string name)
    {
        await Task.CompletedTask;
        var stream = streams.GetValueOrDefault(name);
        if (stream == null)
        {
            stream = new MemoryStream();
            streams[name] = stream;
            return stream;
        }
        else
        {
            var appendable = new MemoryStream();
            appendable.Write(stream.ToArray());
            streams[name] = appendable;
            return appendable;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And there it is. In order to create a writable MemoryStream with content, you cannot do it in the most obvious way. You have to first create a new, empty MemoryStream, then write the existing content to it.

Final code after refactoring:

class BlobContainerStub
{
    readonly Dictionary<string, MemoryStream> streams = [];

    public async Task<Stream> OpenRead(string name)
    {
        await Task.CompletedTask;
        var stream = streams.GetValueOrDefault(name);
        if (stream == null) throw new InvalidOperationException();
        return new MemoryStream(stream.ToArray());
    }

    public async Task<Stream> OpenWrite(string name)
    {
        await Task.CompletedTask;
        var stream = new MemoryStream();
        streams[name] = stream;
        return stream;
    }

    public async Task<Stream> OpenAppend(string name)
    {
        await Task.CompletedTask;
        var stream = streams.GetValueOrDefault(name);
        var appendable = new MemoryStream();

        if (stream != null)
        {
            appendable.Write(stream.ToArray());
        }

        streams[name] = appendable;
        return appendable;
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)