62

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);
Andy Joiner
  • 5,932
  • 3
  • 45
  • 72

6 Answers6

96

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(...);
}
Neil Barnwell
  • 41,080
  • 29
  • 148
  • 220
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
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
particle
  • 3,280
  • 4
  • 27
  • 39
7

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
NoWar
  • 36,338
  • 80
  • 323
  • 498
  • 7
    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?) – Ian Goldby Feb 15 '17 at 10:52
6

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.

Jabbersii
  • 115
  • 2
  • 6
  • 1
    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. https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder.getchunks – AnthonyVO Dec 30 '20 at 14:11
  • 1
    I don't think this is needed, you can just do `streamWriter.WriteAsync(stringBuilder)` – Ramon Smits Apr 08 '21 at 22:28
5

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
Chris Moschini
  • 36,764
  • 19
  • 160
  • 190
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);
    });
}
AnthonyVO
  • 3,821
  • 1
  • 36
  • 41