使用StringBuilder写XML合适吗?

11

感觉有点不对劲。 但也许它并不是...使用StringBuilder编写XML是否可以?我的直觉告诉我:"虽然这样做感觉不对,但它可能非常高效,因为它不会加载额外的库和开销它没有进行XmlWriter调用所需的任何额外方法调用。"总的来说,使用 StringBuilder 似乎代码更少。在使用 XmlWriter 上的好处是什么呢?

这是它的实际样子。 我正在基于您输入的域构建OpenSearch XML文档。

public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "text/xml";

    string domain = WebUtils.ReturnParsedSourceUrl(null); //returns something like www.sample.com
    string cachedChan = context.Cache[domain + "_opensearchdescription"] as String;

    if (cachedChan == null)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        sb.Append("<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">");
        sb.Append("    <ShortName>Search</ShortName>");
        sb.Append("    <Description>Use " + domain + " to search.</Description>");
        sb.Append("    <Contact>contact@sample.com</Contact>");
        sb.Append("    <Url type=\"text/html\" method=\"get\" template=\"http://" + domain + "/Search.aspx?q={searchTerms}\" />");
        sb.Append("    <moz:SearchForm>http://" + domain + "/Search.aspx</moz:SearchForm>");
        sb.Append("    <Image height=\"16\" width=\"16\" type=\"image/x-icon\">http://" + domain + "/favicon.ico</Image>");
        sb.Append("</OpenSearchDescription>");

        cachedChan = sb.ToString();

        context.Cache.Insert(domain + "_opensearchdescription", cachedChan, null, DateTime.Now.AddDays(14), TimeSpan.Zero);
    }

    context.Response.Write(cachedChan);
}

后续,两年后 我意识到我本想表达的是什么,但却完全没有说清楚:使用XML类生成此文件的大量代码与仅使用字符串有何益处?有吗?这比(例如)John Saunder的示例更糟糕吗?

我采用了Jim Schubert的方法,选择“我可以阅读并理解”而不是争取“正确性”。我很高兴这样做了。John Saunder的示例没有错 - 但我觉得它对于我要完成的任务来说过于复杂了。实用主义?也许。


9
不要相信你的直觉,相信你的测量数据和分析工具。 - Haacked
1
@Haacked,方法不错,但几乎没有回答这个非常合理的问题。 - Pavel Radzivilovsky
@Pavel:我写了一篇关于常见字符串拼接方法生成的IL代码的博客文章:http://www.ipreferjim.com/site/2010/03/string-concatenation-in-net-what-really-goes-on/ - Joseph Yaduvanshi
@Jack:你认为XmlWriter会调用哪些“额外方法”?它只专注于编写XML,没有其他的。 - John Saunders
约翰·桑德斯- 真的不知道。我猜(假设,这个词不太好),它是将一堆字符串附加到一个文档中,就像我手动执行的那样,只不过将一个大块字符串转储出来实际上对我来说比使用XmlWriter方法更少手动操作。 - Jack Lawson
显示剩余5条评论
8个回答

16

这是非常错误的做法。使用能够理解XML的.NET API来编写XML。

使用System.Xml.XmlWriter不会因为加载"任何额外的库"而导致性能问题。


使用XML API的原因是它们了解XML的规则。例如,它们将知道需要在元素内引用哪些字符集,以及需要在属性内引用哪些不同的字符集。

在某些情况下可能不是问题:也许您确定domain不会包含任何需要被引用的字符。但在更广泛的情况下,最好让XML API去处理XML——他们知道如何处理XML——这样您就不必自己去做了。


以下是使用LINQ to XML生成有效XML的示例:

public static string MakeXml()
{
    XNamespace xmlns = "http://a9.com/-/spec/opensearch/1.1/";
    XNamespace moz = "http://www.mozilla.org/2006/browser/search/";
    string domain = "http://localhost";
    string searchTerms = "abc";
    var doc = new XDocument(
        new XDeclaration("1.0", "UTF-8", "yes"),
        new XElement(
            xmlns + "OpenSearchDescription",
            new XElement(xmlns + "ShortName", "Search"),
            new XElement(
                xmlns + "Description",
                String.Format("Use {0} to search.", domain)),
            new XElement(xmlns + "Contact", "contact@sample.com"),
            new XElement(
                xmlns + "Url",
                new XAttribute("type", "text/html"),
                new XAttribute("method", "get"),
                new XAttribute(
                    "template",
                    String.Format(
                        "http://{0}/Search.aspx?q={1}",
                        domain,
                        searchTerms))),
            new XElement(
                moz + "SearchForm",
                String.Format("http://{0}/Search.aspx", domain)),
            new XElement(
                xmlns + "Image",
                new XAttribute("height", 16),
                new XAttribute("width", 16),
                new XAttribute("type", "image/x-icon"),
                String.Format("http://{0}/favicon.ico", domain))));
    return doc.ToString(); // If you _must_ have a string
}

1
是和否。这并不是非常错误的,尽管不是由于加载引起的。 - Pavel Radzivilovsky
1
重构为使用XmlWriter的好处是什么?除了“它使用.NET API”之外,对于一个不会改变的小文件有什么好处吗?我认为我现在的代码短小精悍,而且看起来XmlWriter会引入很多额外的冗余代码。 - Jack Lawson
约翰是正确的。使用SB类将在构建第一个无效的XML之前起作用,最有可能是由于遗漏了XML转义序列。但问题更加微妙:OP代码已经是错误的。它声明了UTF-8编码,但实际上并没有使用正确的UTF8文本编写器。现在,您不会将XML传递给任何关心验证的消费者,而是将结果字符串传递给SQL Server XML参数,看看事情是否会爆炸。 - Remus Rusanu
+1 提到转义。我更喜欢使用 System.Linq.Xml API。它非常易读和易于重构。 - Joseph Yaduvanshi
2
好的。我想我已经在这8个答案和所有评论中找到了我的答案。这是我的计划:稍微重构一下并使用Jim Schubert的代码。不过,这也绝对是个答案,因为我认为在其他情况下,你绝对是正确的。不过,我不担心"domain"的值,我已经验证了XML,并且这是一个几乎肯定永远不会改变结构的文件。如果真的有改变,那么我将重构它使用XmlWriter,因为这证明它比我想象的更加灵活。 - Jack Lawson
谢谢你的示例,它有助于查看代码。我确实同意,在大多数情况下,这是最好的选择,因为值和结构可能会发生变化;但对于我的情况,这显示了所有我试图避免的额外“杂质”。我只有一个动态值:domain。我认为LINQ to SQL实际上更难理解;也许是因为我不太熟悉XML,但结构很难想象。有制表符和空格,但元素和属性的工作方式感觉很笨拙和勉强;流程不在那里。但我可以保证,将来我会遵循这种模式处理更动态的XML。 - Jack Lawson

2

对于此问题,我不建议使用StringBuilder,因为你需要为每一行调用Append方法。你可以使用XmlWriter,这不会影响性能。

您可以通过以下方式减少生成的IL代码量:

private const string XML_TEMPLATE = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">
    <ShortName>Search</ShortName>
    <Description>Use {0} to search.</Description>
    <Contact>contact@sample.com</Contact>
    <Url type=\"text/html\" method=\"get\" template=\"http://{0}/Search.aspx?q={searchTerms}\" />
    <moz:SearchForm>http://{0}/Search.aspx</moz:SearchForm>
    <Image height=\"16\" width=\"16\" type=\"image/x-icon\">http://{0}/favicon.ico</Image>
</OpenSearchDescription>";

在你的方法中:

    if (cachedChan == null)
    {
        cachedChan = String.Format(XML_TEMPLATE, domain);

        context.Cache.Insert(domain + "_opensearchdescription", 
               cachedChan, null, DateTime.Now.AddDays(14), TimeSpan.Zero);
    }

这对你很有帮助,因为现在的方法每次 StringBuilder.Append() 调用都需要创建一个新字符串,然后调用该方法。而 String.Format 调用只生成 17 行 IL 代码,相比之下 StringBuilder 生成 8 行构造函数代码,然后每个 Append 调用都需要 6 行代码。尽管如今的技术,多出来的 50 行 IL 代码不会被注意到。


我简直不敢相信我竟然脑抽了,甚至没有想到使用字符串替换。谢谢:D我试图在微观优化和保持可读性的代码之间取得平衡。我认为你的代码做到了这一点。 - Jack Lawson
另外,我刚刚注意到你在括号中使用了“{searchterms}”。为了使其按照我发布的方式工作,你需要将其更改为不再有另一组括号,或者更改String.Format以将这些括号传递到模板中。 - Joseph Yaduvanshi
用{1}替换{searchterms},并将“{searchterms}”作为参数传递,对吗? - Jack Lawson

1

嗯,这很微妙。像生活中的所有其他优化一样,您会打破抽象边界并为此付出代价,以获得效率。

根据我的经验,它确实更快,不是因为加载库(如果有什么作用,那只会使其变慢),而是因为它节省了字符串分配。我不记得具体快多少了,抱歉。使用分析器来测量它将很困难,因为您还可以节省垃圾收集成本。

但是,在您必须处理编码、转义和其他各种问题之前,请不要责怪我,并记得在将这些 XML 放到任何地方之前仔细阅读 XML 标准。


也许吧。这是一个相当小的文档,而且不太可能会改变。 - Jack Lawson

1

嗯,手动编写XML字符串本身并没有什么问题,但它更容易出错。除非您有充分的性能原因来这样做(也就是说,您已经测量并发现XML格式化是瓶颈),否则我会使用XML类。这样可以节省大量调试和开发时间。

顺便说一下,为什么要在生成器调用中混合动态字符串操作?与其这样做,不如:

sb.Append("    <Description>Use " + domain + " to search.</Description>"); 

试试这个:

sb.Append("    <Description>Use ").Append(domain).Append(" to search.</Description>");

可能是缺咖啡了。至少,我会遵循Jim Schubert的建议进行字符串替换。我想到的另一件事就是使用XmlWriter类时额外的冗余代码。似乎我需要添加很多额外的冗长代码才能使用XmlWriter,而我可以简单地将XML转储为字符串;它很小,极不可能改变结构。 - Jack Lawson
我知道你的意思...这需要很多额外的打字工作。如果你确定维护不会成为问题,那就使用字符串吧。 - Peter Ruderman

1
请不要使用StringBuilder。任何告诉你它显著更快的人都没有向你呈现任何真实数据。速度上的差异微不足道,而且你将面临维护的噩梦。
看一下:StringBuilder vs XmlTextWriter

0

你的直觉是错误的。无论你是手写XML还是使用XmlWriter,将XML附加到HttpResponse中的最有效方法是直接将文本附加到响应中。构建整个字符串然后发送它会浪费资源。


这是否在ASP.NET页面生命周期结构中有可能实现? - Pavel Radzivilovsky
1
@Pavel Radzivilovsky:Response.Write 会打破页面生命周期并直接以流的方式写入网络连接,当调用时会立即生效。虽然在这种情况下并不重要,因为页面生命周期并不存在;他正在编写自定义 HTTP 处理程序。 - Randolpho

0

域变量是否会返回"&"字符或其他需要编码的字符?您可能希望花时间进行防御性编程并验证输入。


如果WebUtils.ReturnParsedSourceUrl(null)发生变化,将会有很多东西出现问题。我可以检查每一个可能的异常,但对于这样一个小巧、不显眼的页面来说,这似乎有点过度杀伐了。 - Jack Lawson

-1
你可以创建一个强类型对象,并使用XmlSerialization类生成xml数据。

这对于一个不会在其他地方使用的小文件来说,似乎有些过度杀伐了,不是吗? - Jack Lawson
真的吗?因为这个被踩了?这是一个可接受的解决方案。也许不是你理想中的解决方案,但它确实可以起作用。 - Anthony Shaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接