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