XML Literals and Axis Properties

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Every example is a complete console program that starts with Option Strict On, was built with warnings treated as errors and was run in the .NET 10 SDK container (Linux); the output shown is real output. The examples that are meant to fail say so and show the compiler messages that the SDK printed, with the file path removed. The project-level imports that the SDK adds (System, System.Collections.Generic, System.Linq and others) are why List(Of T) and the query operators need no Imports line. XElement, XDocument and XAttribute need no Imports line either, because the SDK imports System.Xml.Linq for the whole project (the default project imports; see Projects, MSBuild and the dotnet CLI). System.Xml is imported explicitly where XmlReader or XmlConvert is used.

Of the three main .NET languages (C#, F# and Visual Basic), only Visual Basic has XML as part of its syntax. You write the XML where an expression is expected, <book year="1965"><title>Dune</title></book>, and the compiler turns it into calls that build an XElement. You read the XML back with axis properties, which are operators on XElement and on sequences of XElement: .<title> for child elements, .@year for an attribute, ...<tag> for descendants. Together with the query clauses of LINQ and Query Expressions, this makes the generation, reading and transformation of XML very compact. The feature arrived with Visual Basic 9 in Visual Studio 2008. It sits on top of LINQ to XML, which is the .NET API (System.Xml.Linq) shared with C#; the literals and axes are only a syntax for it.

What an XML literal is

An XML literal is an expression, so it can be assigned, passed as an argument, returned and nested. Its type depends on what you write:

Literal Example Type

element

<book id="1">text</book> or <book/>

System.Xml.Linq.XElement

document

starts with <?xml version="1.0"?>

System.Xml.Linq.XDocument

comment

<!-- a comment -->

System.Xml.Linq.XComment

CDATA section

<![CDATA[ 1 < 2 ]]>

System.Xml.Linq.XCData

processing instruction

<?target data?>

System.Xml.Linq.XProcessingInstruction

embedded expression

<%= expression %>, inside any of the above

the value of the expression

The first example shows an element, an empty element, and a document literal with a declaration, a comment, a processing instruction, an element and a CDATA section. The output prints the node types of the document and of the root.

Option Strict On

Module Program
    Sub Main()
        ' An XML literal is an expression of type XElement
        Dim book As XElement = <book isbn="9780441172719" year="1965">
                                   <title>Dune</title>
                                   <author>Frank Herbert</author>
                                   <note/>
                               </book>
        Console.WriteLine(book.GetType().FullName)
        Console.WriteLine(book)

        ' With Option Infer On the declared type can be left out
        Dim empty = <catalog/>
        Console.WriteLine(empty.GetType().Name & " " & empty.IsEmpty)

        ' A document literal starts with the XML declaration and is an XDocument
        Dim doc = <?xml version="1.0" encoding="utf-8" standalone="yes"?>
                  <!-- the shelf catalogue -->
                  <?shelf-tool version="1"?>
                  <catalog>
                      <book>Dune</book>
                      <![CDATA[1 < 2 && 3 > 2]]>
                  </catalog>
        Console.WriteLine(doc.GetType().Name)
        Console.WriteLine(doc.Declaration)
        Console.WriteLine(doc)

        ' Nodes under the document
        For Each n In doc.Nodes()
            Console.WriteLine(n.NodeType.ToString())
        Next
        For Each n In doc.Root.Nodes()
            Console.WriteLine("  root child: " & n.NodeType.ToString())
        Next
    End Sub
End Module
System.Xml.Linq.XElement
<book isbn="9780441172719" year="1965">
  <title>Dune</title>
  <author>Frank Herbert</author>
  <note />
</book>
XElement True
XDocument
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- the shelf catalogue -->
<?shelf-tool version="1"?>
<catalog>
  <book>Dune</book><![CDATA[1 < 2 && 3 > 2]]></catalog>
Comment
ProcessingInstruction
Element
  root child: Element
  root child: CDATA

Observations on the example:

  • A literal that begins with an XML declaration is an XDocument; without a declaration, the outermost element is an XElement. The declaration can carry version, encoding and standalone, and is exposed as Declaration on the document. Because a document literal also contains the comment and the processing instruction, it is the way to write a prolog.

  • The literal is printed as indented XML, whatever indentation the source used: ToString() pretty prints. The one exception in the output is the root of the document literal. Its children are an element followed by a CDATA section, which is a text node, so the element has mixed content and the printer leaves mixed content alone: the CDATA section follows the book element on the same line.

  • The whitespace between elements in the source is layout and is not stored as nodes (the section on white space).

  • The five predefined entity references (&amp;, &lt;, &gt;, &quot;, &apos;) and numeric character references such as &#169; are accepted; any other entity reference, for instance &nbsp;, is BC31180.

  • The literal ends where the XML ends, so a literal can be used inside a larger expression, for instance as an argument. Statement continuation inside a literal is not an issue: line breaks inside XML are part of the XML, not of the VB statement, and the literal may span any number of lines without a continuation character.

The compiler turns a literal into constructor calls

The compiler translates every literal into the functional construction of LINQ to XML: nested New XElement(name, content...) calls, with New XAttribute for attributes. There is no parser at run time. The literal is therefore type checked and fast, a mistake in the XML is a compile error (see the error table below), and the generated code is the same that you would write by hand. In C# the equivalent of the first literal in the example is:

var book = new XElement("book",
    new XAttribute("isbn", "9780441172719"),
    new XAttribute("year", "1965"),
    new XElement("title", "Dune"),
    new XElement("author", "Frank Herbert"),
    new XElement("note"));

This is a valid way to write the same thing in Visual Basic too, and it is what you use when the element names are decided at run time; the literal is the readable form for everything else.

Embedded expressions

An embedded expression <%= expression %> evaluates the Visual Basic expression and inserts the result into the XML. It can appear in these places:

  • as element content: <title><%= b.Title %></title>;

  • as an attribute value: year=<%= b.Year %> (written without quotes);

  • as an element name: <<%= tag %> year="1965">...</>, where the end tag can be shortened to </>;

  • as a whole attribute, or a sequence of attributes, in a start tag: <book <%= New XAttribute("lang", "en") %>>.

What the value may be decides what happens to it: a String or any value that has an XML representation becomes text (escaped for you, so A < B & C is safe); an XElement, an XAttribute, an XComment and the other XNode types are inserted as nodes; and an IEnumerable of any of those, such as the result of a Select query, is inserted item by item. Nothing is special: as an attribute value it removes the attribute, and as content it adds nothing. Numbers, dates and Booleans are converted with the XML rules (the invariant culture, true and false, ISO 8601 dates), not with the current culture; the second program in this section proves it by switching the current culture to German first.

Option Strict On

Public Class Book
    Public Property Title As String
    Public Property Year As Integer
    Public Property Subtitle As String
    Public Sub New(title As String, year As Integer, subtitle As String)
        Me.Title = title
        Me.Year = year
        Me.Subtitle = subtitle
    End Sub
End Class

Module Program
    Sub Main()
        Dim books As New List(Of Book) From {
            New Book("Dune", 1965, Nothing),
            New Book("Emma", 1815, "or, a comedy of manners"),
            New Book("A < B & C", 2001, Nothing)
        }

        ' <%= expression %> embeds the value of any expression in content, attribute values or names
        Dim tag As String = "item"
        Dim first As Book = books(0)
        Dim one = <<%= tag %> year=<%= first.Year %>><%= first.Title %></>
        Console.WriteLine(one)

        ' Nothing as an attribute value omits the attribute; Nothing as content gives an empty element
        Dim shown = <book subtitle=<%= first.Subtitle %> year=<%= first.Year %>><%= first.Subtitle %></book>
        Console.WriteLine(shown)

        ' Text is escaped automatically
        Dim tricky = <title><%= books(2).Title %></title>
        Console.WriteLine(tricky)
        Console.WriteLine(tricky.Value)

        ' A sequence in content becomes a list of children: a query inside the literal
        Dim catalog = <catalog count=<%= books.Count %>>
                          <%= From b In books
                              Order By b.Year
                              Select <book year=<%= b.Year %>><%= b.Title %></book> %>
                      </catalog>
        Console.WriteLine(catalog)

        ' Embedded expressions can nest literals that contain further expressions
        Dim nested = <report>
                         <%= From b In books Where b.Subtitle IsNot Nothing
                             Select <entry>
                                        <title><%= b.Title %></title>
                                        <subtitle><%= b.Subtitle %></subtitle>
                                    </entry> %>
                     </report>
        Console.WriteLine(nested)

        ' Any object is converted with its XML conversion: numbers, dates, booleans and so on
        Dim values = <v n=<%= 3.5 %> d=<%= New Date(2026, 10, 19) %> b=<%= True %>/>
        Console.WriteLine(values)

        ' Embedded expressions of type XElement, XAttribute and String can be mixed in content
        Dim mixed = <p>Hello <%= <b>world</b> %>, <%= New XAttribute("lang", "en") %><%= 42 %>!</p>
        Console.WriteLine(mixed)
    End Sub
End Module
<item year="1965">Dune</item>
<book year="1965" />
<title>A &lt; B &amp; C</title>
A < B & C
<catalog count="3">
  <book year="1815">Emma</book>
  <book year="1965">Dune</book>
  <book year="2001">A &lt; B &amp; C</book>
</catalog>
<report>
  <entry>
    <title>Emma</title>
    <subtitle>or, a comedy of manners</subtitle>
  </entry>
</report>
<v n="3.5" d="2026-10-19T00:00:00" b="true" />
<p lang="en">Hello <b>world</b>, 42!</p>

Things to take from the output:

  • the subtitle attribute of the first book vanished because its value is Nothing, and the empty <book year="1965" /> shows that Nothing content gives an empty element;

  • the title A < B & C came out escaped (&lt;, &amp;) and Value gave the original text back;

  • the query inside the catalog literal produced one book element per object; this nesting of a query inside a literal inside a query is the idiom for turning objects into XML;

  • a Date is written in the XML (2026-10-19T00:00:00) form, a Double with a dot and a Boolean as true.

Option Strict On

Imports System.Globalization

Module Program
    Sub Main()
        CultureInfo.CurrentCulture = New CultureInfo("de-DE")

        ' Embedded values use the XML rules, not the current culture
        Dim price As Double = 12.5
        Dim v = <v n=<%= price %> d=<%= New Date(2026, 10, 19) %> b=<%= True %>/>
        Console.WriteLine(v)
        Console.WriteLine("string concatenation : " & price)

        ' A whole attribute can be embedded in a start tag, and so can a list of attributes
        Dim lang As XAttribute = New XAttribute("lang", "en")
        Dim extras As XAttribute() = {New XAttribute("a", 1), New XAttribute("b", 2)}
        Dim e = <title <%= lang %> <%= extras %>>Dune</title>
        Console.WriteLine(e)

        ' Predefined and numeric character references are accepted
        Dim t = <t>&lt;&amp;&#169;&#x41;</t>
        Console.WriteLine(t.Value)
    End Sub
End Module
<v n="12.5" d="2026-10-19T00:00:00" b="true" />
string concatenation : 12,5
<title lang="en" a="1" b="2">Dune</title>
<&©A

Embedded expressions are ordinary expressions, so everything on Operators and Expressions applies, including If(a, b) to supply a default for Nothing. They may not be placed where the XML syntax does not allow content: an expression outside a literal is BC31172.

XML namespaces

XML namespaces use a different mechanism from .NET namespaces. A file-level Imports <xmlns="uri"> declares the default XML namespace, and Imports <xmlns:p="uri"> declares the prefix p. They are written exactly like an XML namespace declaration, in angle brackets, and they apply to every literal and axis property in the file (not to the project, unless the project file says so: the next example). Names without a prefix then belong to the default namespace; prefixed names use the prefix. GetXmlNamespace(p) returns the XNamespace object for a prefix, and GetXmlNamespace() the default one, which is how you build a name for the method form: shelf.GetName("title").

Option Strict On

Imports <xmlns="http://example.org/shelf">
Imports <xmlns:m="http://example.org/shelf/meta">

Module Program
    Sub Main()
        ' The default namespace from Imports applies to every element name in the literal;
        ' the prefix m is available for names and attributes
        Dim book = <book m:id="42">
                       <title>Dune</title>
                       <m:rating>5</m:rating>
                   </book>
        Console.WriteLine(book)
        Console.WriteLine(book.Name.NamespaceName & " | " & book.Name.LocalName)

        ' Queries use the same names
        Console.WriteLine(book.<title>.Value & " " & book.<m:rating>.Value & " " & book.@m:id)

        ' GetXmlNamespace gives the XNamespace for a prefix (or the default with no argument)
        Dim shelf As System.Xml.Linq.XNamespace = GetXmlNamespace()
        Dim meta As System.Xml.Linq.XNamespace = GetXmlNamespace(m)
        Console.WriteLine(shelf.NamespaceName)
        Console.WriteLine(meta.NamespaceName)
        Console.WriteLine(book.Element(shelf.GetName("title")) IsNot Nothing)
        Console.WriteLine(book.Element("title") Is Nothing)

        ' A literal can declare its own namespaces, which then shadow the imported ones
        Dim local = <other xmlns="http://example.org/other" xmlns:x="urn:x" x:flag="on">
                        <inner/>
                    </other>
        Console.WriteLine(local)
        Console.WriteLine(local.<inner>.Count() & " in the imported default; " &
                          local.Elements().Count() & " child element")
    End Sub
End Module
<book m:id="42" xmlns:m="http://example.org/shelf/meta" xmlns="http://example.org/shelf">
  <title>Dune</title>
  <m:rating>5</m:rating>
</book>
http://example.org/shelf | book
Dune 5 42
http://example.org/shelf
http://example.org/shelf/meta
True
True
<other xmlns="http://example.org/other" xmlns:x="urn:x" x:flag="on">
  <inner />
</other>
0 in the imported default; 1 child element

The important consequences:

  • The Imports namespace is applied to queries as well: book.<title> finds the title element in the default namespace, but book.Element("title") looks for an element in no namespace and finds nothing (the last True of the output). A mismatch between the namespace of the XML data and the namespace in the code is the most common reason for an axis that returns nothing.

  • The root element in the output carries the xmlns declarations: the compiler adds the declarations of the namespaces that the literal uses.

  • A literal may declare its own namespaces with ordinary xmlns attributes, which override the imported ones inside that literal; the last element of the output has its own default namespace, so local.<inner> in the file’s default namespace matches nothing.

  • A prefix that is not imported or declared is an error (BC31148).

Declaring XML namespaces for the whole project

The Visual Basic compiler also accepts XML namespace imports as Import items of the project file, which makes them available in every file without repeating the Imports line. The entries are written with the same text as the statement, with the angle brackets and quotes escaped in the XML of the .vbproj. The program below has no Imports line and still uses the prefix m and the default namespace; the project file is shown after it.

Option Strict On

Module Program
    Sub Main()
        ' This file has no Imports line for the namespace: App.vbproj declares it for the whole project
        Dim book = <book m:id="1"><title>Dune</title></book>
        Console.WriteLine(book.@m:id & " " & book.<title>.Value & " " & book.Name.NamespaceName)
    End Sub
End Module
1 Dune http://example.org/shelf
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <OptionStrict>On</OptionStrict>
  </PropertyGroup>
  <ItemGroup>
    <Import Include="&lt;xmlns=&quot;http://example.org/shelf&quot;&gt;" />
    <Import Include="&lt;xmlns:m=&quot;http://example.org/shelf/meta&quot;&gt;" />
  </ItemGroup>
</Project>

Names that are not Visual Basic identifiers

Inside the angle brackets the name is an XML name, not a VB identifier, so hyphens and dots are allowed (<record-set>, <book.entry>), and a prefix is allowed when it is declared. When the name is only known at run time, build an XName and use the method form (Descendants(name)), because an axis property needs a name that is fixed in the source. The next program covers all of that and the rule that the default namespace is "none" unless you import one.

Option Strict On

Imports <xmlns:dc="http://purl.org/dc/elements/1.1/">

Module Program
    Sub Main()
        ' Names that are valid in XML but not identifiers in VB: hyphens, dots, colons with a declared prefix
        Dim record = <record-set xmlns:dc="http://purl.org/dc/elements/1.1/">
                         <book.entry dc:title="Dune"><dc:creator>Frank Herbert</dc:creator></book.entry>
                         <book.entry dc:title="Emma"><dc:creator>Jane Austen</dc:creator></book.entry>
                     </record-set>
        Console.WriteLine(record.<book.entry>.Count())
        Console.WriteLine(String.Join(",", From e In record.<book.entry> Select e.@dc:title))
        Console.WriteLine(record...<dc:creator>.Count())

        ' Names built at run time need XName and the method form
        Dim dcNs As System.Xml.Linq.XNamespace = GetXmlNamespace(dc)
        Dim dynamicName As System.Xml.Linq.XName = dcNs.GetName("creator")
        Console.WriteLine(record.Descendants(dynamicName).Count())

        ' The default is no namespace: an unprefixed name matches only elements in no namespace
        Dim mixed = <root xmlns="urn:one"><a/><b xmlns=""><a/></b></root>
        Console.WriteLine(mixed...<a>.Count() & " unprefixed a")
        Console.WriteLine(mixed.Descendants(System.Xml.Linq.XName.Get("a", "urn:one")).Count() & " in urn:one")
    End Sub
End Module
2
Dune,Emma
2
2
1 unprefixed a
1 in urn:one

Axis properties

An axis property is an operator on XElement, on XDocument and on IEnumerable(Of XElement). The five forms are:

Form Name Returns Method equivalent

x.<name>

child axis

IEnumerable(Of XElement) of the child elements called name

x.Elements("name")

x.@name

attribute axis

String, the attribute value, or Nothing if the attribute is absent; assignable

x.Attribute("name") and its Value

x...<name>

descendant axis

IEnumerable(Of XElement) of all descendants called name, at any depth

x.Descendants("name")

sequence(i)

extension indexer

the element at index i of the sequence (0-based), or Nothing when i is past the end

sequence.ElementAtOrDefault(i)

sequence.Value

value property

the text of the first element of the sequence, or Nothing for an empty sequence; assignable

First().Value

The axes chain, and each step is applied to every element of the previous sequence: catalog.<book>.<tags>.<tag> is all the tag elements below all tags below all book. The attribute axis and Value applied to a sequence read from its first element. All axis names can have a prefix (.<m:rating>, .@m:id).

Option Strict On

Module Program
    Sub Main()
        Dim catalog As XElement =
            <catalog source="shelf">
                <book isbn="9780441172719" year="1965">
                    <title>Dune</title>
                    <author>Frank Herbert</author>
                    <tags>
                        <tag>classic</tag>
                        <tag>desert</tag>
                    </tags>
                </book>
                <book isbn="9780141439518" year="1815">
                    <title>Emma</title>
                    <author>Jane Austen</author>
                    <tags><tag>classic</tag></tags>
                </book>
                <magazine issue="7"><title>Byte</title></magazine>
            </catalog>

        ' Child axis  x.<name>  : the child elements called name
        Console.WriteLine("children   : " & catalog.<book>.Count())
        ' Attribute axis  x.@name  : the attribute value as a String (Nothing if absent)
        Console.WriteLine("attribute  : " & catalog.@source)
        Console.WriteLine("missing    : " & If(catalog.@owner, "(Nothing)"))
        ' Extension indexer  sequence(i) : the element at index i (0-based)
        Console.WriteLine("indexer    : " & catalog.<book>(1).<title>.Value)
        ' Value  : the concatenated text of the first element in the sequence
        Console.WriteLine("Value      : " & catalog.<book>.<title>.Value)
        ' Descendant axis  x...<name>  : all descendants at any depth
        Console.WriteLine("descendants: " & catalog...<tag>.Count() & " tags, " & catalog...<title>.Count() & " titles")
        Console.WriteLine("all titles : " & String.Join(" | ", From t In catalog...<title> Select t.Value))

        ' The axes chain, and each step works on every element of the previous sequence
        Console.WriteLine("chain      : " & String.Join(",", catalog.<book>.<tags>.<tag>.Select(Function(t) t.Value)))
        Console.WriteLine("attributes : " & String.Join(",", catalog.<book>.Select(Function(b) b.@isbn)))

        ' .@x on a sequence reads the attribute of the first element
        Console.WriteLine("first year : " & catalog.<book>.@year)

        ' Conversion: the values are Strings; convert explicitly
        Dim total As Integer = Aggregate b In catalog.<book> Into Sum(CInt(b.@year))
        Console.WriteLine("sum        : " & total)
        Console.WriteLine("Parse      : " & Integer.Parse(catalog.<book>(0).@year) + 1)

        ' Other forms of attribute access
        Console.WriteLine("Attribute(): " & catalog.<book>(0).Attribute("isbn").Value)
        Console.WriteLine("Elements() : " & catalog.Elements("magazine").Count())
        Console.WriteLine("Descendants: " & catalog.Descendants("tag").Count())
    End Sub
End Module
children   : 2
attribute  : shelf
missing    : (Nothing)
indexer    : Emma
Value      : Dune
descendants: 3 tags, 3 titles
all titles : Dune | Emma | Byte
chain      : classic,desert,classic
attributes : 9780441172719,9780141439518
first year : 1965
sum        : 3780
Parse      : 1966
Attribute(): 9780441172719
Elements() : 1
Descendants: 3

Points to remember:

  • Everything is typed as String. A year is "1965", so a comparison like b.@year > 1900 needs a conversion (CInt(b.@year)), and the conversion rules of Option Strict On apply to the result (BC30512 for an unconverted String). See the section on conversions below.

  • .<name> returns a sequence; .<name>.Value is the first match’s text. When more than one element can match and you need all of them, use a query or Select, not .Value.

  • The descendant axis visits the whole subtree, so on a large document ...<name> is a linear search; prefer the child axis along a known path.

  • The query clauses work over the axes directly: From b In catalog.<book>, Where CInt(b.@year) > 1900, Select b.<title>.Value.

What happens when the XML does not have the shape you expected

Axis properties are designed to be forgiving, because XML is often irregular. A missing child gives an empty sequence, a missing attribute gives Nothing, and .Value of nothing is Nothing. The program shows these, the two things that do throw (the next step after an out-of-range indexer, and an axis applied to a Nothing XElement), and assignments.

Option Strict On

Module Program
    Sub Main()
        Dim doc = <catalog>
                      <book isbn="1"><title>Dune</title></book>
                  </catalog>

        ' Axis properties on missing things do not throw
        Console.WriteLine("missing child count : " & doc.<journal>.Count())
        Console.WriteLine("missing child value : " & If(doc.<journal>.Value, "(Nothing)"))
        Console.WriteLine("missing attribute   : " & If(doc.<book>(0).@missing, "(Nothing)"))
        Console.WriteLine("chain through empty : " & If(doc.<journal>.<title>.Value, "(Nothing)"))

        ' ... but the extension indexer returns Nothing for an index past the end, and the next step then fails
        Dim fifth As XElement = doc.<book>(5)
        Console.WriteLine("indexer out of range: Is Nothing = " & (fifth Is Nothing))
        Try
            Console.WriteLine(doc.<book>(5).@isbn)
        Catch ex As NullReferenceException
            Console.WriteLine("attribute of Nothing : NullReferenceException")
        End Try
        ' Nothing is not an element: the first step works on an Nothing reference
        Dim none As XElement = Nothing
        Try
            Console.WriteLine(none.<book>.Count())
        Catch ex As NullReferenceException
            Console.WriteLine("axis on Nothing     : NullReferenceException")
        End Try

        ' Value and attribute values can be assigned
        doc.<book>(0).@isbn = "9780441172719"
        doc.<book>(0).<title>.Value = "Dune (1965)"
        doc.<book>(0).@lang = "en"
        Console.WriteLine(doc)

        ' Assigning Nothing to an attribute removes it
        doc.<book>(0).@lang = Nothing
        Console.WriteLine(doc.<book>(0).Attributes().Count())

        ' Assigning .Value on an empty sequence
        Try
            doc.<journal>.Value = "x"
            Console.WriteLine("assigned")
        Catch ex As Exception
            Console.WriteLine("assign to empty : " & ex.GetType().Name)
        End Try
    End Sub
End Module
missing child count : 0
missing child value : (Nothing)
missing attribute   : (Nothing)
chain through empty : (Nothing)
indexer out of range: Is Nothing = True
attribute of Nothing : NullReferenceException
axis on Nothing     : NullReferenceException
<catalog>
  <book isbn="9780441172719" lang="en">
    <title>Dune (1965)</title>
  </book>
</catalog>
1
assigned

The rules, from the output:

  • an absent child, an absent attribute, and a chain through an absent element are silent: counts of zero and Nothing;

  • the extension indexer returns Nothing for an out-of-range index instead of throwing, and the next step on that Nothing then fails with NullReferenceException: doc.<book>(5).@isbn throws but doc.<book>(5) does not;

  • an axis applied to an XElement variable that is Nothing throws NullReferenceException, as any member access does;

  • .@name = value sets or adds an attribute, .@name = Nothing removes it, and .Value = text replaces the content of the first element; assigning .Value on an empty sequence is silently ignored, so a typing mistake in a name loses the assignment.

Axis properties and late binding

The axes are resolved at compile time and need a static type of XElement, XDocument or a sequence of XElement. On an Object variable, the compiler does not fall back to late binding: it refuses (BC31168, in the error section). Cast with DirectCast or CType first.

What the compiler generates for .@name

The attribute axis is compiled into a call of a helper class that the compiler adds to the project, a hidden helper type, My.InternalXmlHelper. The listing finds it by reflection and checks that the axes and the method forms give the same results.

Option Strict On

Imports System.Reflection

Module Program
    Sub Main()
        Dim book = <book id="1" year="1965"><title>Dune</title></book>

        ' The compiler-generated helper behind the axis properties lives in the My namespace of the project
        Dim helper As Type = GetType(Program).Assembly.GetTypes().Single(Function(t) t.Name = "InternalXmlHelper")
        Console.WriteLine(helper.FullName & " visible=" & helper.IsPublic & " nested=" & helper.IsNested)
        For Each m In helper.GetMembers(BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.Static Or BindingFlags.DeclaredOnly).
                       Where(Function(x) x.MemberType <> MemberTypes.Constructor).OrderBy(Function(x) x.Name)
            Console.WriteLine("  " & m.MemberType.ToString() & " " & m.Name)
        Next

        ' book.@id is a Value property over the attribute; book.<title> is Elements(name); book...<x> is Descendants(name)
        Console.WriteLine(book.@id = book.Attribute("id").Value)
        Console.WriteLine(book.<title>.SequenceEqual(book.Elements("title")))
    End Sub
End Module
App.My.InternalXmlHelper visible=False nested=False
  Property AttributeValue
  Method get_AttributeValue
  Method set_AttributeValue
True
True

The helper is not visible outside the assembly, lives in the project’s My namespace and exists only in projects that use these features. In this program it contains just AttributeValue, the property that the compiler calls for .@name (get and set); other members are added to it when other features need them. It is generated code, not an API to call, and its shape is not documented, which is why the page shows it by reflection and does not rely on it.

Converting the text of XML

Because axis values are strings, conversion is where bugs hide. CInt of a Nothing string is 0, so a missing attribute reads as zero and goes unnoticed; Integer.Parse of Nothing throws; CDbl and Double.Parse use the current culture, which changes the meaning of 12.50 in a culture that uses the dot as the group separator (1250 in the German culture, shown below). The robust conversions are the explicit ones that XElement itself defines (cast an element or attribute to Integer, Integer? and so on), or XmlConvert, which follows the XML Schema rules and the invariant culture.

Option Strict On

Imports System.Globalization
Imports System.Xml

Module Program
    Sub Main()
        Dim book = <book year="1965" price="12.50" available="true"><pages>412</pages></book>

        ' Axis values are Strings (or Nothing); CInt of Nothing is 0, which hides a missing attribute
        Console.WriteLine("CInt(@year)    : " & CInt(book.@year))
        Console.WriteLine("CInt(@missing) : " & CInt(book.@missing))
        Try
            Console.WriteLine(Integer.Parse(book.@missing))
        Catch ex As ArgumentNullException
            Console.WriteLine("Integer.Parse(Nothing) : ArgumentNullException")
        End Try

        ' XElement defines explicit conversions to the primitive types, to nullable types and to String
        Dim pages As Integer = CInt(book.<pages>(0))
        Console.WriteLine("CInt(element)  : " & pages)
        Dim none As Integer? = CType(book.<chapters>.FirstOrDefault(), Integer?)
        Console.WriteLine("nullable       : " & If(none.HasValue, "has value", "no value"))

        ' Parsing with the XML rules (invariant culture, XML spelling of booleans) is XmlConvert's job
        Console.WriteLine("XmlConvert     : " & XmlConvert.ToDouble(book.@price) & " " & XmlConvert.ToBoolean(book.@available))

        ' CDbl and Double.Parse use the current culture unless told otherwise
        CultureInfo.CurrentCulture = New CultureInfo("de-DE")
        Console.WriteLine("CDbl in de-DE  : " & CDbl(book.@price))
        Console.WriteLine("Invariant      : " & Double.Parse(book.@price, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture))
    End Sub
End Module
CInt(@year)    : 1965
CInt(@missing) : 0
Integer.Parse(Nothing) : ArgumentNullException
CInt(element)  : 412
nullable       : no value
XmlConvert     : 12.5 True
CDbl in de-DE  : 1250
Invariant      : 12.5

Source: XmlConvert

LINQ to XML in Visual Basic

Literals and axes are a syntax over System.Xml.Linq, so every method of XElement, XDocument, XAttribute and XNamespace is available. The query clauses of LINQ and Query Expressions work over the axes. The program filters, orders, groups with Group By ... Into, projects XML into XML and projects XML into plain values.

Option Strict On

Module Program
    Sub Main()
        Dim catalog = <catalog>
                          <book isbn="9780441172719" year="1965" copies="3"><title>Dune</title><author>Frank Herbert</author></book>
                          <book isbn="9780451524935" year="1949" copies="0"><title>1984</title><author>George Orwell</author></book>
                          <book isbn="9780141439518" year="1815" copies="2"><title>Emma</title><author>Jane Austen</author></book>
                          <book isbn="9780141439549" year="1817" copies="1"><title>Persuasion</title><author>Jane Austen</author></book>
                      </catalog>

        ' Queries over axis properties: attributes are Strings, so convert them
        Dim available = From b In catalog.<book>
                        Where CInt(b.@copies) > 0
                        Order By CInt(b.@year) Descending
                        Select Title = b.<title>.Value, Year = CInt(b.@year)
        For Each a In available
            Console.WriteLine(a.Year & " " & a.Title)
        Next

        ' Grouping and aggregating
        Dim perAuthor = From b In catalog.<book>
                        Group By Author = b.<author>.Value Into Titles = Count(), Copies = Sum(CInt(b.@copies))
                        Order By Author
        For Each p In perAuthor
            Console.WriteLine(p.Author & ": " & p.Titles & " title(s), " & p.Copies & " copies")
        Next

        ' XML to XML: a projection written as a literal with embedded queries
        Dim report = <report generated="2026-10-19">
                         <%= From b In catalog.<book>
                             Where CInt(b.@year) < 1900
                             Select <old title=<%= b.<title>.Value %> by=<%= b.<author>.Value %>/> %>
                     </report>
        Console.WriteLine(report)

        ' XML to objects, then objects back to XML
        Dim isbns As List(Of String) = (From b In catalog.<book> Select b.@isbn).ToList()
        Console.WriteLine(String.Join(" ", isbns))

        ' Query with XName strings and a namespace-free Elements() mix
        Dim titlesViaMethod = catalog.Elements("book").Elements("title").Select(Function(t) t.Value)
        Console.WriteLine(String.Join(",", titlesViaMethod))
    End Sub
End Module
1965 Dune
1817 Persuasion
1815 Emma
Frank Herbert: 1 title(s), 3 copies
George Orwell: 1 title(s), 0 copies
Jane Austen: 2 title(s), 3 copies
<report generated="2026-10-19">
  <old title="Emma" by="Jane Austen" />
  <old title="Persuasion" by="Jane Austen" />
</report>
9780441172719 9780451524935 9780141439518 9780141439549
Dune,1984,Emma,Persuasion

Reading, writing and changing the shelf XML catalogue

The shelf catalogue can be stored as an XML file. Writing uses a document literal and Save; reading uses XDocument.Load or XElement.Load; changes are method calls (Add, Remove, ReplaceWith, SetAttributeValue) or assignments to the axes. The program writes the file in the temporary directory, reads it back through the axes, modifies it and deletes it.

Option Strict On

Imports System.IO

Module Program
    Sub Main()
        Dim filePath As String = Path.Combine(Path.GetTempPath(), "shelf-catalogue-sample.xml")

        ' Writing: build a document from objects and save it
        Dim titles As String() = {"Dune", "Emma", "Ulysses"}
        Dim doc = <?xml version="1.0" encoding="utf-8"?>
                  <catalog>
                      <%= From t In titles.Select(Function(x, i) (Title:=x, Id:=i + 1))
                          Select <book id=<%= t.Id %>><title><%= t.Title %></title></book> %>
                  </catalog>
        doc.Save(filePath)
        Console.WriteLine(File.ReadAllLines(filePath)(0))

        ' Reading: Load keeps an XDocument; Root is the element
        Dim loaded As XDocument = XDocument.Load(filePath)
        For Each b In loaded.<catalog>.<book>
            Console.WriteLine(b.@id & " " & b.<title>.Value)
        Next
        Console.WriteLine(loaded.Root.Name)

        ' XElement.Load works the same way and returns the root element
        Dim root As XElement = XElement.Load(filePath)
        Console.WriteLine(root.<book>.Count())

        ' Adding, replacing and removing
        root.Add(<book id="4"><title>Walden</title></book>)
        root.<book>(0).Remove()
        root.<book>.Where(Function(b) b.@id = "2").Single().ReplaceWith(<book id="2"><title>Emma (annotated)</title></book>)
        root.SetAttributeValue("updated", "2026-10-19")
        Console.WriteLine(root.ToString(SaveOptions.DisableFormatting))

        File.Delete(filePath)
    End Sub
End Module
<?xml version="1.0" encoding="utf-8"?>
1 Dune
2 Emma
3 Ulysses
catalog
3
<catalog updated="2026-10-19"><book id="2"><title>Emma (annotated)</title></book><book id="3"><title>Ulysses</title></book><book id="4"><title>Walden</title></book></catalog>

Notes: XDocument.Load returns the document, whose <catalog> axis finds the root element by name (the document, not the root, is the sequence’s starting point); XElement.Load returns the root element itself. Both read the whole file into memory. For a very large file that must be processed in one pass, use XmlReader and create XElement objects for the pieces you need with XNode.ReadFrom; for sequential writing, use XmlWriter. Both are covered with the other serializers on Serialization: JSON and XML. Files and paths are on Files, Directories and Streams.

Validation and untrusted XML

XDocument.Validate checks a tree against an XSD XmlSchemaSet; the validation handler receives each problem, with the exact schema message. Untrusted input must be parsed with care. XElement.Parse and XDocument.Parse accept a DOCTYPE, but they do not resolve external entities (an external entity expands to nothing, as the Parse line of the output shows). An XmlReader created with default settings prohibits DTDs altogether and throws. If a document comes from outside, create the reader with explicit XmlReaderSettings, leave DtdProcessing on Prohibit (or Ignore), keep XmlResolver unset, and set limits.

Option Strict On

Imports System.Xml
Imports System.Xml.Schema

Module Program
    Sub Main()
        ' Validation against an XSD
        Dim schemaText As String =
            "<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"">" &
            "<xs:element name=""book""><xs:complexType><xs:sequence>" &
            "<xs:element name=""title"" type=""xs:string""/>" &
            "</xs:sequence><xs:attribute name=""year"" type=""xs:int"" use=""required""/></xs:complexType></xs:element>" &
            "</xs:schema>"
        Dim schemas As New XmlSchemaSet()
        schemas.Add("", XmlReader.Create(New IO.StringReader(schemaText)))

        Dim good = <book year="1965"><title>Dune</title></book>
        Dim bad = <book year="old"><title>Dune</title></book>
        For Each candidate In {good, bad}
            Dim problems As New List(Of String)
            Dim document As New XDocument(candidate)
            document.Validate(schemas, Sub(sender, e) problems.Add(e.Message))
            Console.WriteLine(If(problems.Count = 0, "valid", "invalid: " & problems(0)))
        Next

        ' The DOCTYPE of untrusted input: XElement.Parse accepts it but does not resolve external entities;
        ' XmlReader.Create prohibits it by default
        Dim hostile As String =
            "<!DOCTYPE d [<!ENTITY secret SYSTEM ""file:///etc/hostname"">]><d>&secret;</d>"
        Console.WriteLine("Parse     : [" & XElement.Parse(hostile).Value & "]")
        Try
            Using reader = XmlReader.Create(New IO.StringReader(hostile))
                XElement.Load(reader)
            End Using
        Catch ex As XmlException
            Console.WriteLine("XmlReader : " & ex.Message)
        End Try
    End Sub
End Module
valid
invalid: The 'year' attribute is invalid - The value 'old' is invalid according to its datatype 'http://www.w3.org/2001/XMLSchema:int' - The string 'old' is not a valid Int32 value.
Parse     : []
XmlReader : For security reasons DTD is prohibited in this XML document. To enable DTD processing set the DtdProcessing property on XmlReaderSettings to Parse and pass the settings into XmlReader.Create method.

White space

Visual Basic treats white space in a literal by a simple rule: white space that consists only of blank characters between two tags (or between a tag and an embedded expression) is layout and is dropped; text that contains any non-blank character keeps every character of it, including line breaks and indentation. This is what lets literals be indented naturally in code. The program also shows the parser side: XElement.Parse drops layout white space by default and LoadOptions.PreserveWhitespace keeps it, and the output side: ToString() indents, SaveOptions.DisableFormatting does not.

Option Strict On

Module Program
    Sub Main()
        ' Whitespace between elements in a literal is layout, not content...
        Dim a = <p>
                    <b>x</b>
                    <i>y</i>
                </p>
        Console.WriteLine(a.Nodes().Count() & " nodes")
        Console.WriteLine(a.ToString(SaveOptions.DisableFormatting))

        ' ... while text mixed with elements keeps its spaces inside the line
        Dim b = <p>one <b>two</b>  three</p>
        Console.WriteLine(b.ToString(SaveOptions.DisableFormatting))
        Console.WriteLine("[" & b.Value & "]")

        ' Text that has any non-blank character keeps every character of it, including line breaks and indentation
        Dim c = <t>
                    hello
                </t>
        Console.WriteLine("[" & c.Value & "]")

        ' Embedded expression results keep all of their white space
        Dim padded As String = "  keep  me  "
        Dim d = <t><%= padded %></t>
        Console.WriteLine("[" & d.Value & "]")

        ' Parse keeps layout only when asked to
        Dim text As String = "<p>" & vbLf & "  <b>x</b>" & vbLf & "</p>"
        Console.WriteLine(XElement.Parse(text).Nodes().Count())
        Console.WriteLine(XElement.Parse(text, LoadOptions.PreserveWhitespace).Nodes().Count())

        ' Output formatting
        Dim e = <p><b>x</b></p>
        Console.WriteLine(e.ToString())
        Console.WriteLine(e.ToString(SaveOptions.DisableFormatting))
    End Sub
End Module
2 nodes
<p><b>x</b><i>y</i></p>
<p>one <b>two</b>  three</p>
[one two  three]
[
                    hello
                ]
[  keep  me  ]
1
3
<p>
  <b>x</b>
</p>
<p><b>x</b></p>

Reading the output: the literal with the elements on separate lines holds just two nodes (b and i); in the mixed-content literal the spaces around two are part of the text; the text that was on its own line inside <t> keeps its line breaks and indentation, which is rarely what you want (write it on one line, or trim it); an embedded String keeps all of its blanks; and the parser needs PreserveWhitespace to keep the layout between tags.

Choosing an XML API

API Use it for In Visual Basic

XML literals and axes over LINQ to XML

documents that fit in memory, generating and transforming XML, configuration and catalogue files

the idiomatic choice; no equivalent in C#

XmlReader and XmlWriter

very large or streamed XML, forward-only processing, precise control of encoding and settings

same API; combine with XNode.ReadFrom to get literals' benefits for each fragment

XmlDocument (DOM) and XPath

existing code, SelectNodes with XPath, in-place editing with node events

supported, but the document is mutable and verbose; prefer XDocument for new code

XmlSerializer, DataContractSerializer

mapping XML to and from classes

see the serialization page; no literals involved

XSLT (XslCompiledTransform)

stylesheets that already exist

same API; LINQ to XML is usually shorter than XSLT in code you control

Differences from C#

Topic Visual Basic C#

Creating XML

literals with embedded expressions

new XElement("name", new XAttribute(...), content) (functional construction)

Reading XML

axis properties .<x>, .@x, ...<x>

Elements("x"), Attribute("x"), Descendants("x") and explicit casts

XML namespaces

Imports <xmlns:p="...">, GetXmlNamespace

XNamespace ns = "..."; ns.GetName("x")

Missing attribute or child

Nothing or empty sequence, no exception

null (attribute) or empty sequence; .Value on a null element throws

Value conversions

strings; CInt, XmlConvert, explicit casts

explicit casts (int)element and (int?)attribute are the idiom

Query integration

query clauses run directly over axis properties

LINQ methods or query syntax over Elements() and Descendants()

C# has no XML literal and no axis syntax, and C# code that works with XML builds trees with XElement constructors and reads them with methods and casts. A Visual Basic project and a C# project can exchange XElement objects without conversion because the type is the same; see Interoperating with C# and Other .NET Languages.

Error reference

The compiler checks literals while it parses them. Syntax errors stop the compile before semantic analysis, so each of the following programs contains one mistake (all are within Sub Main of an otherwise valid module).

A mismatched or missing end tag. The same mistake produces four messages, because the parser reports the element that is missing its end tag and then the confusion that follows:

Option Strict On

Module Program
    Sub Main()
        Dim a = <a><b></a></b>
    End Sub
End Module
Program.vb(5,20): error BC31151: Element is missing an end tag.
Program.vb(5,23): error BC30636: '>' expected.
Program.vb(5,23): error BC31146: XML name expected.
Program.vb(5,23): error BC31165: Expected beginning '<' for an XML tag.

An undeclared prefix, a duplicate attribute, an entity other than the predefined five, an embedded expression outside a literal, and an unquoted attribute value:

Option Strict On

Module Program
    Sub Main()
        Dim a = <m:a/>
    End Sub
End Module
Program.vb(5,18): error BC31148: XML namespace prefix 'm' is not defined.
Option Strict On

Module Program
    Sub Main()
        Dim a = <a x="1" x="2"/>
    End Sub
End Module
Program.vb(5,26): error BC31149: Duplicate XML attribute 'x'.
Option Strict On

Module Program
    Sub Main()
        Dim a = <a>&nbsp;</a>
    End Sub
End Module
Program.vb(5,20): error BC31180: XML entity references are not supported.
Option Strict On

Module Program
    Sub Main()
        Dim v = <%= 1 %>
    End Sub
End Module
Program.vb(5,17): error BC31172: An embedded expression cannot be used here.
Option Strict On

Module Program
    Sub Main()
        Dim a = <a x=1/>
    End Sub
End Module
Program.vb(5,22): error BC31166: Expected quoted XML attribute value or embedded expression.

An element that is never closed, which also breaks the surrounding Sub and Module:

Option Strict On

Module Program
    Sub Main()
        Dim a = <a>
        Console.WriteLine(a)
    End Sub
End Module
Program.vb(3,1): error BC30625: 'Module' statement must end with a matching 'End Module'.
Program.vb(4,5): error BC30026: 'End Sub' expected.
Program.vb(5,17): error BC31151: Element is missing an end tag.
Program.vb(9,11): error BC30636: '>' expected.
Program.vb(9,11): error BC31165: Expected beginning '<' for an XML tag.

Axis properties on an Object (late binding is not supported) and a String assigned to an Integer under Option Strict On:

Option Strict On

Module Program
    Sub Main()
        Dim o As Object = <a x="1"/>
        Dim v = o.@x
        Dim w = o.<b>
        Dim n As Integer = CType(<a x="1"/>, XElement).@x
    End Sub
End Module
Program.vb(6,17): error BC31168: XML axis properties do not support late binding.
Program.vb(7,17): error BC31168: XML axis properties do not support late binding.
Program.vb(8,28): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Integer'.

Assignments and conversions on axis results:

Option Strict On

Module Program
    Sub Main()
        Dim a = <a x="1" y="2"/>
        Dim s As String = a.@x
        Dim t As Integer = a.@x
        Dim u As String = a.<b>
        Dim e As XElement = a...<b>
        a.@z = 3
    End Sub
End Module
Program.vb(10,16): error BC30512: Option Strict On disallows implicit conversions from 'Integer' to 'String'.
Program.vb(7,28): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Integer'.
Program.vb(8,27): error BC30512: Option Strict On disallows implicit conversions from 'IEnumerable(Of XElement)' to 'String'.
Program.vb(8,27): warning BC42361: Cannot convert 'IEnumerable(Of XElement)' to 'String'. You can use the 'Value' property to get the string value of the first element of 'IEnumerable(Of XElement)'.
Program.vb(9,29): error BC30512: Option Strict On disallows implicit conversions from 'IEnumerable(Of XElement)' to 'XElement'.

An axis result is not a variable:

Option Strict On

Module Program
    Sub Main()
        Dim a = <a>text</a>
        a.<b>(0) = <c/>
    End Sub
End Module
Program.vb(6,9): error BC30068: Expression is a value and therefore cannot be the target of an assignment.
Code Cause Fix

BC31151

an element has no end tag, or the end tag does not match the start tag

close the tags in the right order

BC30636, BC31146, BC31165

follow-on errors of a malformed tag: > expected, XML name expected, expected < for an XML tag

fix the first error first

BC31148

the XML namespace prefix is not defined

add Imports <xmlns:p="uri"> or an xmlns:p attribute

BC31149

a duplicate attribute in one start tag

remove it

BC31166

an attribute value is neither quoted nor an embedded expression

quote it or use <%= value %>

BC31172

an embedded expression where XML syntax does not allow one

move it inside a literal

BC31180

an entity reference other than the five predefined ones

use the character itself, or an embedded expression

BC31168

an XML axis on a late-bound (Object) value

cast to XElement

BC30512

an axis value (String or IEnumerable(Of XElement)) assigned to another type with Option Strict On

convert, or take .Value of a sequence

BC42361

warning: a sequence of elements is converted to String; the message suggests .Value

take .Value

BC30068

an axis result is a value and cannot be assigned

assign to .Value or to an attribute instead

Pitfalls

  • Namespace mismatch. A query returns nothing when the code’s default namespace and the document’s differ; compare Name and Name.NamespaceName of what you actually loaded. The default namespace applies to element names only, never to attribute names without a prefix.

  • Text with line breaks. Text that sits on its own lines inside an element keeps the line breaks and indentation. Keep short text on one line.

  • Strings everywhere. Convert deliberately and use the invariant culture; a missing attribute reads as 0 through CInt.

  • Out-of-range indexer. The element index returns Nothing; test it before dereferencing, or use FirstOrDefault.

  • Nothing removes attributes. Nothing as an embedded attribute value omits the attribute, which may differ from what a schema expects; use If(value, "") to write an empty attribute.

  • Parse versus literal. A literal cannot be built from a string; for text that arrives at run time, call XElement.Parse or XDocument.Load (and read the section on security above).

  • Large documents. LINQ to XML holds the whole tree in memory; stream with XmlReader when the document is large.

  • Axis on Nothing. The first step on a null XElement throws.

Where the books are outdated

The two books consulted for this section describe XML mostly through the older XmlDocument, XmlReader, XmlWriter and XmlSerializer classes and through LINQ to XML in a single example. Compared with the current platform:

  • Literals and axis properties are shown as a curiosity or not at all. They are the central, idiomatic XML feature of the language, and the first-class way to read and write the shelf catalogue; this page treats them completely.

  • XmlDocument and XPath still work and are supported, but new code should prefer XDocument, and parsing of untrusted XML needs the settings described in the validation section; a book listing that loads untrusted XML with defaults from the .NET Framework era is not a safe pattern.

  • Syntax borrowed from other languages sometimes appears in listings, for instance C# attribute syntax for serialization attributes; the Visual Basic forms are <Serializable> and, for XML, .@name and .<name>.