59

I am trying to write an object to an Xml string and take that string and save it to a DB. But first I need to get the string...

    private static readonly Encoding LocalEncoding = Encoding.UTF8;

    public static string SaveToString<T> (T settings)
    {
        Stream stream = null;
        TextWriter writer = null;
        string settingsString = null;

        try
        {
            stream = new MemoryStream();

            var serializer = new XmlSerializer(typeof(T));

            writer = new StreamWriter(stream, LocalEncoding);

            serializer.Serialize(writer, settings);

            var buffer = new byte[stream.Length];

            stream.Read(buffer, 0, (int)stream.Length);
            
            settingsString = LocalEncoding.GetString(buffer);
        }
        catch (Exception ex)
        {
            // If the action cancels we don't want to throw, just return null.
        }
        finally
        {
            if (stream != null)
                stream.Close();

            if (writer != null)
                writer.Close();
        }

        return settingsString;
    }

This seems to work, the stream gets filled with bytes. But when I come to read it back into the buffer and then into the string... the buffer is filled with '0'! Not sure what I doing wrong here guys.

tigerswithguitars
  • 2,497
  • 1
  • 31
  • 53
  • 1
    possible duplicate of [How do you get a string from a MemoryStream?](http://stackoverflow.com/questions/78181/how-do-you-get-a-string-from-a-memorystream) – andyp May 07 '14 at 20:52

4 Answers4

94

If you'd checked the results of stream.Read, you'd have seen that it hadn't read anything - because you haven't rewound the stream. (You could do this with stream.Position = 0;.) However, it's easier to just call ToArray:

settingsString = LocalEncoding.GetString(stream.ToArray());

(You'll need to change the type of stream from Stream to MemoryStream, but that's okay as it's in the same method where you create it.)

Alternatively - and even more simply - just use StringWriter instead of StreamWriter. You'll need to create a subclass if you want to use UTF-8 instead of UTF-16, but that's pretty easy. See this answer for an example.

I'm concerned by the way you're just catching Exception and assuming that it means something harmless, by the way - without even logging anything. Note that using statements are generally cleaner than writing explicit finally blocks.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks :) . That's quick work. I know the using blocks are more usual. But I quite like the flatness this gives me, no nested usings. Which is only a style thing. I didn't know there was a StringWriter, seems much easier, but I like the ability to specify the encoing. I'll probably add an overload for this, rather than a new class. – tigerswithguitars May 04 '12 at 09:40
  • @tigerswithguitars: How exactly are you intending to add an overload to `StringWriter` itself? The point of the new class is that `StringWriter` always returns UTF-16 from its `TextWriter.Encoding` property; the extra class just overrides that property. – Jon Skeet May 04 '12 at 09:43
  • sorry, my wording was confusing. I will add an overload to my own method to specify the encoding. – tigerswithguitars May 04 '12 at 09:54
  • @tigerswithguitars: Right, that's fine. You'll still need to add the extra `StringWriter` class, but that's just to *use* within the method. – Jon Skeet May 04 '12 at 10:08
  • I have just finished your book... and someone up voted this today. I had forgotten You had answered this. Amazing coincidence. Also, "C# in Depth" is the BEST language book I have ever read, amazing stuff. – tigerswithguitars Feb 26 '14 at 11:34
  • @tigerswithguitars: Very glad you enjoyed it :) – Jon Skeet Feb 26 '14 at 11:38
43
string result = System.Text.Encoding.UTF8.GetString(fs.ToArray());
Prasanth
  • 3,029
  • 31
  • 44
Farzad J
  • 1,089
  • 2
  • 14
  • 28
14
string result = Encoding.UTF8.GetString((stream as MemoryStream).ToArray());
evorios
  • 126
  • 1
  • 5
  • 1
    I would question the use of `as` here - it's not protecting you from anything. Firstly, you know the stream is a `MemoryStream` anyway, plus, even if it wasn't, passing `null` into `GetString` will throw an exception, so you may as well use an explicit cast in the first place. – Simon MᶜKenzie Oct 19 '15 at 23:00
  • 1
    While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – ryanyuyu Oct 20 '15 at 13:50
8

In case of a very large stream length there is the hazard of memory leak due to Large Object Heap. i.e. The byte buffer created by stream.ToArray creates a copy of memory stream in Heap memory leading to duplication of reserved memory. I would suggest to use a StreamReader, a TextWriter and read the stream in chunks of char buffers.

In netstandard2.0 System.IO.StreamReader has a method ReadBlock

you can use this method in order to read the instance of a Stream (a MemoryStream instance as well since Stream is the super of MemoryStream):

private static string ReadStreamInChunks(Stream stream, int chunkLength)
{
    stream.Seek(0, SeekOrigin.Begin);
    string result;
    using(var textWriter = new StringWriter())
    using (var reader = new StreamReader(stream))
    {
        var readChunk = new char[chunkLength];
        int readChunkLength;
        //do while: is useful for the last iteration in case readChunkLength < chunkLength
        do
        {
            readChunkLength = reader.ReadBlock(readChunk, 0, chunkLength);
            textWriter.Write(readChunk,0,readChunkLength);
        } while (readChunkLength > 0);

        result = textWriter.ToString();
    }

    return result;
}

NB. The hazard of memory leak is not fully eradicated, due to the usage of MemoryStream, that can lead to memory leak for large memory stream instance (memoryStreamInstance.Size >85000 bytes). You can use Recyclable Memory stream, in order to avoid LOH. This is the relevant library

George Kargakis
  • 4,940
  • 3
  • 16
  • 12