Samo
Samo

Reputation: 8240

Why does StreamWriter need to be open to access my MemoryStream?

I have some test code that's preparing a MemoryStream that will eventually be read by an object. Here's how I want to write it:

        var manager = new LeaderboardImportManager(leaderboard);
        var columnNames = manager.ColumnNames;

        var stream = new MemoryStream();
        using (var writer = new StreamWriter(stream))
        {
            writer.WriteLine(string.Join(",", columnNames));
            foreach (var user in users)
            {
                var row = leaderboard.Metrics.Select(m => Faker.RandomNumber.Next().ToString()).ToList();
                row.Insert(0, user.UserName);
                writer.WriteLine(string.Join(",", row));
            }

            writer.Flush();
            stream.Position = 0;
        }

        return stream;

But when I do it that way, my stream object becomes unreadable and my test fails, so I have to do it like this:

        var manager = new LeaderboardImportManager(leaderboard);
        var columnNames = manager.ColumnNames;

        var stream = new MemoryStream();
        var writer = new StreamWriter(stream);

        writer.WriteLine(string.Join(",", columnNames));
        foreach (var user in users)
        {
            var row = leaderboard.Metrics.Select(m => Faker.RandomNumber.Next().ToString()).ToList();
            row.Insert(0, user.UserName);
            writer.WriteLine(string.Join(",", row));
        }

        writer.Flush();
        stream.Position = 0;
        return stream;

This, of course, prevents me from being able to dispose of my StreamWriter object, which as I understand it, should definitely be disposed of.

Why does the StreamWriter need to remain open if I've flushed its contents to the MemoryStream object already?

I can think of some very inconvenient ways to work around this, but I'd like to know why it doesn't work the way I want it, and whether or not there's something I can do to make it work that way. Any advice is appreciated, thanks!

Upvotes: 1

Views: 764

Answers (4)

Croll
Croll

Reputation: 3791

using operator calls Dispose method on object before exiting the operator block { }.

On disposal, StreamWriter disposes underlying Stream too.

This means your Stream is an invalidated object before it gets returned.

Apply using statement only for objects created and destroyed in current scope (do not return them at least).

Why does the StreamWriter need to remain open if I've flushed its contents to the MemoryStream object already?

As @mikez mentioned, by default created StreamWriter "owns" the underlying stream, but you can avoid this behaviour by adding leaveOpen = true in constructor.

new StreamWriter(stream = s, encoding = Encoding.UTF8, bufferSize = 128, leaveOpen = true)

Upvotes: 2

David Anderson
David Anderson

Reputation: 8616

StreamWriter automatically closes the Stream if you don't tell it not to. Create it like this instead to leave the Stream open:

using (var writer = new StreamWriter(stream, System.Text.Encoding.UTF8, 1024, true))

Alternatively, if you don't wish to pass the extra arguments, use GetBuffer to access MemoryStream's internal buffer. Avoid ToArray as this creates a copy of the data and depending on your scenario may be inefficient.

Upvotes: 2

Samo
Samo

Reputation: 8240

The other answers indicate I should use the constructor that has a leaveOpen param and set it to true, but I dislike this because the constructor also requires a bufferSize argument.

However, I realized that I can get away with this just as easily:

        // new method body, returns byte array

        var stream = new MemoryStream();
        using (var writer = new StreamWriter(stream))
        {
            writer.WriteLine(string.Join(",", columnNames));
            foreach (var user in users)
            {
                var row = leaderboard.Metrics.Select(m => Faker.RandomNumber.Next().ToString()).ToList();
                row.Insert(0, user.UserName);
                writer.WriteLine(string.Join(",", row));
            }

            writer.Flush();
            stream.Position = 0;
        }

        return stream.ToArray();

        // consumer opens a new stream using the bytes

        using (var stream = new MemoryStream(this.GetCSVStream(leaderboard, users)))
        {
            mockFile.Setup(f => f.InputStream).Returns(stream);
            this.service.UpdateEntries(update.ID, mockFile.Object);
        }

Upvotes: 0

Mike Zboray
Mike Zboray

Reputation: 40838

By default, the StreamWriter "owns" the stream it is passed and will close it when disposed. Use the constructor that has a leaveOpen boolean parameter. Set it to true to avoid closing the underlying Stream when the writer is disposed in your first example.

Upvotes: 3

Related Questions