Как использовать StringWriter и HtmlWriter вместе без предупреждений анализа кода
Я использую.net и мне нужно получить html-текст, поэтому я решил использовать HtmlTextWriter и StringWriter вместе, чтобы получить правильно сформированный html. Но несмотря на все разные способы написания кода, я все равно получаю предупреждения от статического анализатора кода (используя Microsoft All Rules). В примерах кода ниже я показываю предупреждение анализатора кода в комментарии. Чтобы упростить код, я на самом деле не обращаюсь к HtmlTextWriter (вы увидите комментарий об этом в каждой функции). Как правильно написать код, чтобы избежать предупреждений?
// 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 ответа
Измените ваш Func5 следующим образом:
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;
}
Ключ должен установить для переменной stringWriter значение null (что не влияет на InnerWriter экземпляра HtmlTextWriter), а затем использовать InnerWriter.ToString() для получения HTML.
На самом деле это всего лишь модифицированная версия примера в статье MSDN, на которую есть ссылка в предыдущем комментарии, но которая относится конкретно к вашему использованию.
На самом деле нет способа заставить этот код избегать предупреждений, потому что в данном конкретном случае анализ кода является неправильным.
Правильный код - Func3, добавляющий атрибут CodeAnalysis.SuppressMessage:
// 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;
}
Документация для CA2202 использует пример того, как StreamWriter утилизирует свой Stream, что правильно, но HtmlTextWriter не избавляется от своего внутреннего TextWriter (это можно проверить с помощью создания подкласса StringWriter и установки точки останова в переопределении Dispose). Это немного сбивает с толку, поскольку HtmlTextWriter является производным от TextWriter, а StringWriter также является производным от TextWriter (в отличие от StreamWriter и его Stream, являющихся двумя совершенно разными классами), так почему HtmlTextWriter нужен InnerWriter?... но в любом случае, именно так он и работает,
Кроме того, в документации говорится, что не следует подавлять это предупреждение, потому что "Даже если известно, что Dispose для объекта безопасно вызывается несколько раз, реализация может измениться в будущем". Однако в этом случае Dispose не вызывается несколько раз, поэтому предупреждение может быть безопасно подавлено.
Но не верьте мне на слово! Вот доказательство:
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);
}
}
}
Поскольку StringWriter является одноразовым, вы можете обернуть свой внутренний писатель другим способом.
using (StringWriter stringWriter = new StringWriter())
{
using (var writer = new HtmlTextWriter(stringWriter))
{
html = stringWriter.ToString();
}
}
return html;