Best way to get InnerXml of an XElement?

Mike Powell picture Mike Powell · Aug 6, 2008 · Viewed 82k times · Source

What's the best way to get the contents of the mixed body element in the code below? The element might contain either XHTML or text, but I just want its contents in string form. The XmlElement type has the InnerXml property which is exactly what I'm after.

The code as written almost does what I want, but includes the surrounding <body>...</body> element, which I don't want.

XDocument doc = XDocument.Load(new StreamReader(s));
var templates = from t in doc.Descendants("template")
                where t.Attribute("name").Value == templateName
                select new
                {
                   Subject = t.Element("subject").Value,
                   Body = t.Element("body").ToString()
                };

Answer

Luke Sampson picture Luke Sampson · Nov 10, 2009

I wanted to see which of these suggested solutions performed best, so I ran some comparative tests. Out of interest, I also compared the LINQ methods to the plain old System.Xml method suggested by Greg. The variation was interesting and not what I expected, with the slowest methods being more than 3 times slower than the fastest.

The results ordered by fastest to slowest:

  1. CreateReader - Instance Hunter (0.113 seconds)
  2. Plain old System.Xml - Greg Hurlman (0.134 seconds)
  3. Aggregate with string concatenation - Mike Powell (0.324 seconds)
  4. StringBuilder - Vin (0.333 seconds)
  5. String.Join on array - Terry (0.360 seconds)
  6. String.Concat on array - Marcin Kosieradzki (0.364)

Method

I used a single XML document with 20 identical nodes (called 'hint'):

<hint>
  <strong>Thinking of using a fake address?</strong>
  <br />
  Please don't. If we can't verify your address we might just
  have to reject your application.
</hint>

The numbers shown as seconds above are the result of extracting the "inner XML" of the 20 nodes, 1000 times in a row, and taking the average (mean) of 5 runs. I didn't include the time it took to load and parse the XML into an XmlDocument (for the System.Xml method) or XDocument (for all the others).

The LINQ algorithms I used were: (C# - all take an XElement "parent" and return the inner XML string)

CreateReader:

var reader = parent.CreateReader();
reader.MoveToContent();

return reader.ReadInnerXml();

Aggregate with string concatenation:

return parent.Nodes().Aggregate("", (b, node) => b += node.ToString());

StringBuilder:

StringBuilder sb = new StringBuilder();

foreach(var node in parent.Nodes()) {
    sb.Append(node.ToString());
}

return sb.ToString();

String.Join on array:

return String.Join("", parent.Nodes().Select(x => x.ToString()).ToArray());

String.Concat on array:

return String.Concat(parent.Nodes().Select(x => x.ToString()).ToArray());

I haven't shown the "Plain old System.Xml" algorithm here as it's just calling .InnerXml on nodes.


Conclusion

If performance is important (e.g. lots of XML, parsed frequently), I'd use Daniel's CreateReader method every time. If you're just doing a few queries, you might want to use Mike's more concise Aggregate method.

If you're using XML on large elements with lots of nodes (maybe 100's), you'd probably start to see the benefit of using StringBuilder over the Aggregate method, but not over CreateReader. I don't think the Join and Concat methods would ever be more efficient in these conditions because of the penalty of converting a large list to a large array (even obvious here with smaller lists).