How to use StringWriter and HtmlWriter together without Code Analysis warnings

4.4k views Asked by At

I'm using .net and need to get some html text, so I thought I would use the HtmlTextWriter and StringWriter together to get the well-formed html. But despite all the different ways I write the code I still get warnings from the static code analyzer (using Microsoft All Rules). In the code examples below I show the code analyzer warning in a comment. To simplify the code I don't actually make any calls to the HtmlTextWriter (you'll see a comment to that effect in each function). How can I write the code properly to avoid the warnings?

// CA2000 : Microsoft.Reliability : In method 'Default.Func1()', object 'stringWriter' is not disposed along all exception paths. Call System.IDisposable.Dispose on object 'stringWriter' before all references to it are out of scope.
public static string Func1()
{
    string html;
    StringWriter stringWriter;
    using (var writer = new HtmlTextWriter(stringWriter = new StringWriter()))
    {
        // You would do some stuff with the writer here, but not for this example.

        html = stringWriter.ToString();
    }
    return html;
}

// CA2202 : Microsoft.Usage : Object 'stringWriter' can be disposed more than once in method 'Default.Func2()'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.: Lines: 45
public static string Func2()
{
    string html;
    StringWriter stringWriter = null;
    try
    {
        using (var writer = new HtmlTextWriter(stringWriter = new StringWriter()))
        {
            // You would do some stuff with the writer here, but not for this example.

            html = stringWriter.ToString();
        }
    }
    finally
    {
        if (stringWriter != null)
            stringWriter.Dispose();
    }
    return html;
}

// CA2202 : Microsoft.Usage : Object 'stringWriter' can be disposed more than once in
// method 'Default.Func3()'. To avoid generating a System.ObjectDisposedException 
// you should not call Dispose more than one time on an object.: Lines: 61
public static string Func3()
{
    string html;
    using (var stringWriter = new StringWriter())
    {
        using (var writer = new HtmlTextWriter(stringWriter))
        {
            // You would do some stuff with the writer here, but not for this example.

            html = stringWriter.ToString();
        }
    }
    return html;
}

// CA2202 : Microsoft.Usage : Object 'stringWriter' can be disposed more than once in 
// method 'Default.Func4()'. To avoid generating a System.ObjectDisposedException you 
// should not call Dispose more than one time on an object.: Lines: 77
public static string Func4()
{
    string html;
    using (StringWriter stringWriter = new StringWriter())
    {
        using (HtmlTextWriter writer = new HtmlTextWriter(stringWriter))
        {
            // You would do some stuff with the writer here, but not for this example.

            html = stringWriter.ToString();
        }
    }
    return html;
}

// CA2202 : Microsoft.Usage : Object 'stringWriter' can be disposed more than once in 
// method 'Default.Func5()'. To avoid generating a System.ObjectDisposedException you 
// should not call Dispose more than one time on an object.: Lines: 100
public static string Func5()
{
    string html;
    StringWriter stringWriter = null;
    try
    {
        stringWriter = new StringWriter();
        using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter))
        {
            // You would do some stuff with the writer here, but not for this example.

            html = stringWriter.ToString();
        }
    }
    finally
    {
        if (stringWriter != null)
            stringWriter.Dispose();
    }
    return html;
}
3

There are 3 answers

3
Paul Farry On

Because StringWriter is Disposable you can wrap your inner writer with another using.

using (StringWriter stringWriter = new StringWriter())
{
    using (var writer = new HtmlTextWriter(stringWriter))
    {
         html = stringWriter.ToString();  
    }
}
return html;
0
Harlow Burgess On

There is actually no way to make this code avoid the warnings because in this particular case, the code analysis is wrong.

The correct code is Func3, adding a CodeAnalysis.SuppressMessage attribute:

// Code Analysis is incorrectly assuming that HtmlTextWriter.Dispose will dispose of the InnerWriter, but it actually does not.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static string Func3()
{
    string html;
    using (var stringWriter = new StringWriter())
    {
        using (var writer = new HtmlTextWriter(stringWriter))
        {
            // You would do some stuff with the writer here, but not for this example.

            // I prefer to use writer.InnerWriter as opposed to stringWriter for clarity.
            html = writer.InnerWriter.ToString();
        }
    }
    return html;
}

The documentation for CA2202 uses an example of a StreamWriter disposing of its Stream, which is correct, but HtmlTextWriter does not dispose of its inner TextWriter (verifiable by subclassing StringWriter and setting a breakpoint in override Dispose). This is a bit confusing, since HtmlTextWriter derives from TextWriter, and StringWriter also derives from TextWriter (as opposed to a StreamWriter and its Stream being two completely different classes) so why does HtmlTextWriter need an InnerWriter?… but anyway, this is how it works.

Also, the documentation says not to suppress this warning because “Even if Dispose for the object is known to be safely callable multiple times, the implementation might change in the future.” However, in this case Dispose is not being called multiple times, so the warning can be safely suppressed.

But don't take my word for it! Here is the proof:

using System;
using System.IO;
using System.Web.UI;

namespace WebApplication1
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            StreamWillBeDisposed();
            TextWriterWillNotBeDisposed();
        }

        public static void StreamWillBeDisposed()
        {
            Stream stream = new DebugMemoryStream();
            using (StreamWriter writer = new StreamWriter(stream))
            {
                // Use the writer object...
            }// Underlying Stream will be disposed here by the StreamWriter
        }

        public static void TextWriterWillNotBeDisposed()
        {
            TextWriter stringWriter = new DebugStringWriter();
            using (HtmlTextWriter writer = new HtmlTextWriter(stringWriter))
            {
                // Use the writer object...
            }// Underlying TextWriter will NOT be disposed here by the HtmlTextWriter
        }
    }


    public class DebugMemoryStream : MemoryStream
    {
        protected override void Dispose(bool disposing)
        {
            // This Stream will be disposed when the StreamWriter is disposed
            System.Diagnostics.Debugger.Break();
            base.Dispose(disposing);
        }
    }

    public class DebugStringWriter : StringWriter
    {
        protected override void Dispose(bool disposing)
        {
            // This code will never see the light of day
            System.Diagnostics.Debugger.Break();
            base.Dispose(disposing);
        }
    }

}
0
Christopher King On

Modify your Func5 to be as follows:

public static string Func5()
{
    string html;
    StringWriter stringWriter = null;
    try
    {
        stringWriter = new StringWriter();
        using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter))
        {
            stringWriter = null;

            // You would do some stuff with the writer here, but not for this example.

            html = htmlTextWriter.InnerWriter.ToString();
        }
    }
    finally
    {
        if (stringWriter != null)
            stringWriter.Dispose();
    }
    return html;
}

The key is to set the stringWriter variable to null (which does not impact the InnerWriter of the HtmlTextWriter instance) and then use the InnerWriter.ToString() to get the HTML.

This is really just a modifed version of the sample in the MSDN article referenced in a previous comment but applied specifically to your usage.