62

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.

1

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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: 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.
sorry, my wording was confusing. I will add an overload to my own method to specify the encoding.
@tigerswithguitars: Right, that's fine. You'll still need to add the extra StringWriter class, but that's just to use within the method.
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.
|
46
string result = System.Text.Encoding.UTF8.GetString(fs.ToArray());

3 Comments

While this code sample may possibly answer the question, it would be preferable to include some the essential explanation to the answer here.
What is stem here? If a variable, how did you you instantiate it? Same question for fs.
The user has mistakenly used stem instead of System
16
string result = Encoding.UTF8.GetString((stream as MemoryStream).ToArray());

2 Comments

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.
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.
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

1 Comment

Yes - use StreamReader - but not because of memory leaks, but because it's just the best way to do it!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.