Instantiating an X-DOM in C#

Rather than using the Load or Parse methods, you can build an X-DOM tree by
manually instantiating objects and adding them to a parent via XContainer’s Add
method.

To construct an XElement and XAttribute, simply provide a name and value:

XElement lastName = new XElement ("lastname", "Bloggs");
lastName.Add (new XComment ("nice name"));
XElement customer = new XElement ("customer");
customer.Add (new XAttribute ("id", 123));
customer.Add (new XElement ("firstname", "Joe"));
customer.Add (lastName);
Console.WriteLine (customer.ToString());
The result:
<customer id="123">
<firstname>Joe</firstname>
<lastname>Bloggs<!--nice name--></lastname>
</customer>

A value is optional when constructing an XElement—you can provide just the element
name and add content later. Notice that when we did provide a value, a simple string
sufficed—we didn’t need to explicitly create and add an XText child node. The XDOM
does this work automatically, so you can deal simply with “values.”

Functional Construction

In our preceding example, it’s hard to glean the XML structure from the code. XDOM
supports another mode of instantiation, called functional construction (from
functional programming). With functional construction, you build an entire tree in
a single expression:

XElement customer =
new XElement ("customer", new XAttribute ("id", 123),
new XElement ("firstname", "joe"),
new XElement ("lastname", "bloggs",
new XComment ("nice name")
)
);

This has two benefits. First, the code resembles the shape of the XML. Second, it
can be incorporated into the select clause of a LINQ query. For example, the following
LINQ to SQL query projects directly into an X-DOM:

XElement query =
new XElement ("customers",
from c in dataContext.Customers
select
new XElement ("customer", new XAttribute ("id", c.ID),
new XElement ("firstname", c.FirstName),

new XElement ("lastname", c.LastName,
new XComment ("nice name")
)
)
);

Specifying Content

Functional construction is possible because the constructors for XElement (and
XDocument) are overloaded to accept a params object array:
public XElement (XName name, params object[] content)
The same holds true for the Add method in XContainer:
public void Add (params object[] content)
Hence, you can specify any number of child objects of any type when building or
appending an X-DOM. This works because anything counts as legal content. To see
how, we need to examine how each content object is processed internally. Here are
the decisions made by XContainer, in order:
1. If the object is null, it’s ignored.
2. If the object is based on XNode or XStreamingElement, it’s added as is to the
Nodes collection.
3. If the object is an XAttribute, it’s added to the Attributes collection.
4. If the object is a string, it gets wrapped in an XText node and added to Nodes.*
5. If the object implements IEnumerable, it’s enumerated, and the same rules are
applied to each element.
6. Otherwise, the object is converted to a string, wrapped in an XText node, and
then added to Nodes.†
Everything ends up in one of two buckets: Nodes or Attributes. Furthermore, any
object is valid content because it can always ultimately call ToString on it and treat
it as an XText node.

Before calling ToString on an arbitrary type, XContainer first
tests whether it is one of the following types:
float, double, decimal, bool,
DateTime, DateTimeOffset, TimeSpan
If so, it calls an appropriate typed ToString method on the
XmlConvert helper class instead of calling ToString on the object
itself. This ensures that the data is round-trippable and compliant
with standard XML formatting rules.

Automatic Deep Cloning

When a node or attribute is added to an element (whether via functional construction
or an Add method), the node or attribute’s Parent property is set to that element.
A node can have only one parent element: if you add an already parented node to a
second parent, the node is automatically deep-cloned. In the following example, each
customer has a separate copy of address:
var address = new XElement ("address",
new XElement ("street", "Lawley St"),
new XElement ("town", "North Beach")
);
var customer1 = new XElement ("customer1", address);
var customer2 = new XElement ("customer2", address);
customer1.Element ("address").Element ("street").Value = "Another St";
Console.WriteLine (
customer2.Element ("address").Element ("street").Value); // Lawley St
This automatic duplication keeps X-DOM object instantiation free of side effects—
another hallmark of functional programming.

Navigating and Querying

As you might expect, the XNode and XContainer classes define methods and properties
for traversing the X-DOM tree. Unlike a conventional DOM, however, these functions
don’t return a collection that implements IList<T>. Instead, they return either
a single value or a sequence that implements IEnumerable<T>—upon which you are
then expected to execute a LINQ query (or enumerate with a foreach). This allows
for advanced queries as well as simple navigation tasks—using familiar LINQ query
syntax.