63

What is the best method of writing a StringBuilder to a System.IO.Stream?

I am currently doing:

StringBuilder message = new StringBuilder("All your base");
message.Append(" are belong to us");

System.IO.MemoryStream stream = new System.IO.MemoryStream();
System.Text.ASCIIEncoding encoding = new ASCIIEncoding();
stream.Write(encoder.GetBytes(message.ToString()), 0, message.Length);

6 Answers 6

101

Don't use a StringBuilder, if you're writing to a stream, do just that with a StreamWriter:

using (var memoryStream = new MemoryStream())
using (var writer = new StreamWriter(memoryStream ))
{
    // Various for loops etc as necessary that will ultimately do this:
    writer.Write(...);
}
Sign up to request clarification or add additional context in comments.

3 Comments

It's not feasible if he needs to use the string for something else before writing it though.
Absolutely true, but of course he hasn't said that's a requirement, so I didn't make any assumptions.
Also remember to Flush and Seek before reading from the stream.
18

That is the best method. Other wise loss the StringBuilder and use something like following:

using (MemoryStream ms = new MemoryStream())
{
    using (StreamWriter sw = new StreamWriter(ms, Encoding.Unicode))
    {
        sw.WriteLine("dirty world.");
    }
    //do somthing with ms
}

Comments

8

Perhaps it will be usefull.

var sb= new StringBuilder("All your money");
sb.Append(" are belong to us, dude.");
var myString = sb.ToString();
var myByteArray = System.Text.Encoding.UTF8.GetBytes(myString);
var ms = new MemoryStream(myByteArray);
// Do what you need with MemoryStream

2 Comments

Copy first to a string, then copy again to a byte array, then finally copy to the stream? Really? Surely this can be done succinctly without making any intermediate copy. (What if the string builder is 2GB?)
This was the answer I needed for my use case, thank you.
8

EDIT: As @ramon-smits points out, if you have access to StringBuilder.GetChunks(), you will also have access to StreamWriter.WriteAsync(StringBuilder). So you can just do this instead:

StringBuilder stringBuilder = new StringBuilder();
// Write data to StringBuilder...
Stream stream = GetStream(); // Get output stream from somewhere.
using (var streamWriter = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true))
{
    await streamWriter.WriteAsync(stringBuilder);
}

Much simpler.


I recently had to do exactly this thing and found this question with unsatisfactory answers.

You can write a StringBuilder to a Stream without materializing the entire string:

StringBuilder stringBuilder = new StringBuilder();
// Write data to StringBuilder...
Stream stream = GetStream(); // Get output stream from somewhere.
using (var streamWriter = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true))
{
    foreach (ReadOnlyMemory<char> chunk in stringBuilder.GetChunks())
    {
        await streamWriter.WriteAsync(chunk);
    }
}

N.B. This API (StringBuilder.GetChunks()) is only available in .NET Core 3.0 and above

If this operation happens frequently, you could further reduce GC pressure by using a StringBuilder object pool.

3 Comments

This is a cool way to extract the data from a StringBuilder. One warning is that this is not thread safe so you should place a lock around this block of code. learn.microsoft.com/en-us/dotnet/api/…
I don't think this is needed, you can just do streamWriter.WriteAsync(stringBuilder)
Async is not intended as a way to ensure thread safety, and abusing it for that purpose will lead you to writing some terrible buggy code. Thread safety is ensured through resource locking mechanisms, as suggested by @AnythonVO
6

Depending on your use case it may also make sense to just start with a StringWriter:

StringBuilder sb = null;

// StringWriter - a TextWriter backed by a StringBuilder
using (var writer = new StringWriter())
{
    writer.WriteLine("Blah");
    . . .
    sb = writer.GetStringBuilder(); // Get the backing StringBuilder out
}

// Do whatever you want with the StringBuilder

1 Comment

1

If you want to use something like a StringBuilder because it is cleaner to pass around and work with, then you can use something like the following StringBuilder alternate I created.

The most important thing it does different is that it allows access to the internal data without having to assemble it into a String or ByteArray first. This means you don't have to double up the memory requirements and risk trying to allocate a contiguous chunk of memory that fits your entire object.

NOTE: I am sure there are better options then using a List<string>() internally but this was simple and proved to be good enough for my purposes.

public class StringBuilderEx
{
    List<string> data = new List<string>();
    public void Append(string input)
    {
        data.Add(input);
    }
    public void AppendLine(string input)
    {
        data.Add(input + "\n");
    }
    public void AppendLine()
    {
        data.Add("\n");
    }

    /// <summary>
    /// Copies all data to a String.
    /// Warning: Will fail with an OutOfMemoryException if the data is too
    /// large to fit into a single contiguous string.
    /// </summary>
    public override string ToString()
    {
        return String.Join("", data);
    }

    /// <summary>
    /// Process Each section of the data in place.   This avoids the
    /// memory pressure of exporting everything to another contiguous
    /// block of memory before processing.
    /// </summary>
    public void ForEach(Action<string> processData)
    {
        foreach (string item in data)
            processData(item);
    }
}

Now you can dump the entire contents to file using the following code.

var stringData = new StringBuilderEx();
stringData.Append("Add lots of data");

using (StreamWriter file = new System.IO.StreamWriter(localFilename))
{
    stringData.ForEach((data) =>
    {
        file.Write(data);
    });
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.