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 |
|
|
document |
starts with |
|
comment |
|
|
CDATA section |
|
|
processing instruction |
|
|
embedded expression |
|
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
Source: XML Element Literal, XML Document Literal
Observations on the example:
-
A literal that begins with an XML declaration is an
XDocument; without a declaration, the outermost element is anXElement. The declaration can carryversion,encodingandstandalone, and is exposed asDeclarationon 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 thebookelement 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 (
&,<,>,",') and numeric character references such as©are accepted; any other entity reference, for instance , 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 < B & C</title>
A < B & C
<catalog count="3">
<book year="1815">Emma</book>
<book year="1965">Dune</book>
<book year="2001">A < B & 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>
Source: Embedded Expressions in XML
Things to take from the output:
-
the
subtitleattribute of the first book vanished because its value isNothing, and the empty<book year="1965" />shows thatNothingcontent gives an empty element; -
the title
A < B & Ccame out escaped (<,&) andValuegave the original text back; -
the query inside the
catalogliteral produced onebookelement per object; this nesting of a query inside a literal inside a query is the idiom for turning objects into XML; -
a
Dateis written in the XML (2026-10-19T00:00:00) form, aDoublewith a dot and aBooleanastrue.
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><&©A</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
Source: XML Element Literal
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
Importsnamespace is applied to queries as well:book.<title>finds thetitleelement in the default namespace, butbook.Element("title")looks for an element in no namespace and finds nothing (the lastTrueof 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
xmlnsdeclarations: the compiler adds the declarations of the namespaces that the literal uses. -
A literal may declare its own namespaces with ordinary
xmlnsattributes, which override the imported ones inside that literal; the last element of the output has its own default namespace, solocal.<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="<xmlns="http://example.org/shelf">" />
<Import Include="<xmlns:m="http://example.org/shelf/meta">" />
</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
Source: XML Child Axis Property
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 |
|---|---|---|---|
|
child axis |
|
|
|
attribute axis |
|
|
|
descendant axis |
|
|
|
extension indexer |
the element at index |
|
|
value property |
the text of the first element of the sequence, or |
|
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
Source: XML Child Axis Property, XML Attribute Axis Property, XML Descendant Axis Property, Extension Indexer Property
Points to remember:
-
Everything is typed as
String. A year is"1965", so a comparison likeb.@year > 1900needs a conversion (CInt(b.@year)), and the conversion rules ofOption Strict Onapply to the result (BC30512 for an unconvertedString). See the section on conversions below. -
.<name>returns a sequence;.<name>.Valueis the first match’s text. When more than one element can match and you need all of them, use a query orSelect, 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
Source: XML Value Property
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
Nothingfor an out-of-range index instead of throwing, and the next step on thatNothingthen fails withNullReferenceException:doc.<book>(5).@isbnthrows butdoc.<book>(5)does not; -
an axis applied to an
XElementvariable that isNothingthrowsNullReferenceException, as any member access does; -
.@name = valuesets or adds an attribute,.@name = Nothingremoves it, and.Value = textreplaces the content of the first element; assigning.Valueon 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
Source: XML Attribute Axis Property
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>
Source: Manipulating XML in Visual Basic
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# |
|
very large or streamed XML, forward-only processing, precise control of encoding and settings |
same API; combine with |
|
existing code, |
supported, but the document is mutable and verbose; prefer |
|
mapping XML to and from classes |
see the serialization page; no literals involved |
XSLT ( |
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 |
|
Reading XML |
axis properties |
|
XML namespaces |
|
|
Missing attribute or child |
|
|
Value conversions |
strings; |
explicit casts |
Query integration |
query clauses run directly over axis properties |
LINQ methods or query syntax over |
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> </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: |
fix the first error first |
BC31148 |
the XML namespace prefix is not defined |
add |
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 |
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 ( |
cast to |
BC30512 |
an axis value ( |
convert, or take |
BC42361 |
warning: a sequence of elements is converted to |
take |
BC30068 |
an axis result is a value and cannot be assigned |
assign to |
Pitfalls
-
Namespace mismatch. A query returns nothing when the code’s default namespace and the document’s differ; compare
NameandName.NamespaceNameof 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
0throughCInt. -
Out-of-range indexer. The element index returns
Nothing; test it before dereferencing, or useFirstOrDefault. -
Nothing removes attributes.
Nothingas an embedded attribute value omits the attribute, which may differ from what a schema expects; useIf(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.ParseorXDocument.Load(and read the section on security above). -
Large documents. LINQ to XML holds the whole tree in memory; stream with
XmlReaderwhen the document is large. -
Axis on
Nothing. The first step on a nullXElementthrows.
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.
-
XmlDocumentand XPath still work and are supported, but new code should preferXDocument, 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,.@nameand.<name>.
References
-
XML (Visual Basic Programming Guide): Overview of LINQ to XML in Visual Basic, Creating XML, Accessing XML and Manipulating XML
-
XML Literals and XML Axis Properties (Language Reference)
-
Imports Statement (XML Namespace) and GetXmlNamespace Operator
-
LINQ to XML overview, Functional construction, Namespaces overview and LINQ to XML security
-
XElement, XDocument and XNamespace
-
Visual Basic Language Specification (XML expressions)