C# XmlReader
last modified October 18, 2026
In this article we show how to read XML data in C# with XmlReader.
XmlReader
XmlReader represents a reader that provides fast, non-cached,
forward-only access to XML data. It reads the document node by node, so it needs
little memory regardless of the size of the document. The price is that we cannot
go back or jump around; for random access and modification, use
XmlDocument.
XmlReader is an abstract class in the System.Xml
namespace. We create instances with the static Create method, which
accepts a file name or URI, a Stream, or a TextReader,
and optionally an XmlReaderSettings object.
C# XmlReader example
The following example creates a simple XmlReader.
<?xml version="1.0" encoding="utf-8"?> <value>6</value>
We have a very simple XML file.
using System.Xml;
using var reader = XmlReader.Create("data.xml");
reader.MoveToContent();
var data = reader.ReadElementContentAsString();
Console.WriteLine(data);
// the typed variant converts the text for us
using var reader2 = XmlReader.Create("data.xml");
reader2.MoveToContent();
int value = reader2.ReadElementContentAsInt();
Console.WriteLine(value * 7);
In the example, we read the value from the XML document with
XmlReader.
using var reader = XmlReader.Create("data.xml");
The XmlReader is created with the Create method. The
using declaration closes the underlying file when the reader goes out
of scope.
reader.MoveToContent();
The MoveToContent method skips the non-content nodes, such as the XML
declaration, comments, and white space, and moves to the first content node.
var data = reader.ReadElementContentAsString();
The ReadElementContentAsString method reads the current element and
returns its text content as a string.
int value = reader2.ReadElementContentAsInt();
There are typed variants for the common types: ReadElementContentAsInt,
AsLong, AsDecimal, AsDouble,
AsBoolean, and AsDateTime. They convert the text with
XML-schema rules, which are culture-independent, so a decimal point is always a
period.
$ dotnet run 6 42
C# XmlReader ReadToFollowing
The ReadToFollowing method advances the reader to the next following
element that matches the specified name and returns true if a
matching element is found.
<bookstore>
<book genre='Science Fiction'>
<title>Dune</title>
<author>Frank Herbert</author>
<price>8.99</price>
</book>
<book genre='Novel'>
<title>Old Goriot</title>
<author>Honoré de Balzac</author>
<price>9.0</price>
</book>
</bookstore>
This XML file contains books.
using System.Xml;
using var reader = XmlReader.Create("books.xml");
while (reader.ReadToFollowing("book"))
{
Console.WriteLine($"genre: {reader.GetAttribute("genre")}");
reader.ReadToFollowing("title");
Console.WriteLine($"title: {reader.ReadElementContentAsString()}");
reader.ReadToFollowing("author");
Console.WriteLine($"author: {reader.ReadElementContentAsString()}");
reader.ReadToFollowing("price");
Console.WriteLine($"price: {reader.ReadElementContentAsDecimal()}");
Console.WriteLine("-------------------------");
}
The example reads two books from the books.xml file.
while (reader.ReadToFollowing("book"))
{
We advance to each book element in turn. When there are no more books,
the method returns false and the loop ends.
Console.WriteLine($"genre: {reader.GetAttribute("genre")}");
The GetAttribute method reads an attribute of the current element by
name, without moving the reader.
reader.ReadToFollowing("title");
Console.WriteLine($"title: {reader.ReadElementContentAsString()}");
We read the contents of the title element. The
ReadElementContentAs... methods leave the reader after the end tag of
the element.
$ dotnet run genre: Science Fiction title: Dune author: Frank Herbert price: 8.99 ------------------------- genre: Novel title: Old Goriot author: Honoré de Balzac price: 9.0 -------------------------
This approach depends on the order of the elements in the file. If a book lacked
an author, ReadToFollowing("author") would run on into the
next book. For documents with optional parts, use the node-by-node approach shown
below.
C# XmlReader Read
The Read method reads the next node from the stream. It returns
false at the end of the document.
<?xml version="1.0" encoding="utf-8"?>
<products>
<product>
<id>1</id>
<name>Product A</name>
<price>1200</price>
</product>
<product>
<id>2</id>
<name>Product B</name>
<price>1100</price>
</product>
</products>
In this XML file, we have products.
using System.Xml;
Console.WriteLine("Products");
Console.WriteLine("----------------------");
var settings = new XmlReaderSettings { IgnoreWhitespace = true };
using var reader = XmlReader.Create("products.xml", settings);
reader.MoveToContent(); // skip the declaration, stop on <products>
while (reader.Read())
{
string result = reader.NodeType switch
{
XmlNodeType.Element when reader.Name == "product" => $"{reader.Name}\n",
XmlNodeType.Element => $"{reader.Name}: ",
XmlNodeType.Text => $"{reader.Value}\n",
XmlNodeType.EndElement when reader.Name == "product" => "----------------------\n",
_ => ""
};
Console.Write(result);
}
The example presents the classic approach to reading XML files. We read the
document node by node and deal with elements, text, and other node types in a
switch expression.
var settings = new XmlReaderSettings { IgnoreWhitespace = true };
By default, the white space between elements is reported as Whitespace
nodes. We ask the reader to skip it.
reader.MoveToContent();
We skip the XML declaration and position the reader on the root element.
string result = reader.NodeType switch
{
XmlNodeType.Element when reader.Name == "product" => $"{reader.Name}\n",
XmlNodeType.Element => $"{reader.Name}: ",
XmlNodeType.Text => $"{reader.Value}\n",
XmlNodeType.EndElement when reader.Name == "product" => "----------------------\n",
_ => ""
};
We use a switch expression to process the different node types. The
Name property gives the name of the current node, and the
Value property gets its text value.
$ dotnet run Products ---------------------- product id: 1 name: Product A price: 1200 ---------------------- product id: 2 name: Product B price: 1100 ----------------------
C# XmlReader node types and attributes
To see what the reader reports, we print every node of a small document with its type and depth. The example also shows how to read attributes.
using System.Xml;
string xml = """
<?xml version="1.0"?>
<!-- a comment -->
<items>
<item id="1" type="a">first</item>
<item id="2" type="b"/>
<![CDATA[raw <text>]]>
</items>
""";
using var reader = XmlReader.Create(new StringReader(xml));
while (reader.Read())
{
Console.Write($"{reader.NodeType,-15} depth={reader.Depth}");
if (reader.NodeType == XmlNodeType.Element)
{
Console.Write($" name={reader.Name} empty={reader.IsEmptyElement} attrs={reader.AttributeCount}");
if (reader.HasAttributes)
{
while (reader.MoveToNextAttribute())
{
Console.Write($" [{reader.Name}={reader.Value}]");
}
reader.MoveToElement();
}
}
else if (reader.HasValue)
{
Console.Write($" value='{reader.Value.Trim()}'");
}
Console.WriteLine();
}
The document contains a declaration, a comment, elements with attributes, an empty element, and a CDATA section.
if (reader.HasAttributes)
{
while (reader.MoveToNextAttribute())
{
Console.Write($" [{reader.Name}={reader.Value}]");
}
reader.MoveToElement();
}
MoveToNextAttribute moves the reader from the element to its
attributes one by one. When we are done, MoveToElement moves back to the
owning element; otherwise the next Read would continue from the last
attribute. The IsEmptyElement property is true for an element written
as <item/>, which has no end tag node.
$ dotnet run XmlDeclaration depth=0 value='version="1.0"' Whitespace depth=0 value='' Comment depth=0 value='a comment' Whitespace depth=0 value='' Element depth=0 name=items empty=False attrs=0 Whitespace depth=1 value='' Element depth=1 name=item empty=False attrs=2 [id=1] [type=a] Text depth=2 value='first' EndElement depth=1 Whitespace depth=1 value='' Element depth=1 name=item empty=True attrs=2 [id=2] [type=b] Whitespace depth=1 value='' CDATA depth=1 value='raw <text>' Whitespace depth=1 value='' EndElement depth=0
C# XmlReaderSettings
The XmlReaderSettings class configures the reader. The most used
properties are IgnoreWhitespace, IgnoreComments,
IgnoreProcessingInstructions, DtdProcessing,
ValidationType, and Async.
using System.Xml;
var settings = new XmlReaderSettings
{
IgnoreWhitespace = true,
IgnoreComments = true,
IgnoreProcessingInstructions = true
};
string xml = """
<?xml version="1.0"?>
<!-- ignored -->
<items>
<item>one</item>
<item>two</item>
</items>
""";
using var reader = XmlReader.Create(new StringReader(xml), settings);
int nodes = 0;
while (reader.Read())
{
nodes++;
}
Console.WriteLine($"{nodes} nodes");
We count the nodes the reader reports with white space, comments and processing instructions ignored.
$ dotnet run 9 nodes
The nodes are the declaration, the items element, two
item elements with their text and end tags, and the final end tag. The
XML declaration is still reported.
By default, the reader prohibits DTDs, which protects against entity expansion
attacks. Do not enable DtdProcessing.Parse for untrusted input. The
reader can also validate against an XML schema; see the
XmlDocument tutorial.
C# XmlReader streaming large files
The real strength of XmlReader is processing files that are too big to
load into a DOM. We can combine it with an iterator method to turn the XML into a
lazy sequence of objects, and use ReadSubtree to confine the reading
of each record.
using System.Xml;
foreach (var p in ReadProducts("products.xml"))
{
Console.WriteLine(p);
}
// a lazy sequence: only one product is in memory at a time, even for a huge file
static IEnumerable<Product> ReadProducts(string path)
{
using var reader = XmlReader.Create(path);
reader.MoveToContent();
while (reader.ReadToFollowing("product"))
{
using XmlReader sub = reader.ReadSubtree();
sub.Read();
int id = 0;
string name = "";
decimal price = 0;
while (sub.Read())
{
if (sub.NodeType != XmlNodeType.Element)
{
continue;
}
switch (sub.Name)
{
case "id": id = sub.ReadElementContentAsInt(); break;
case "name": name = sub.ReadElementContentAsString(); break;
case "price": price = sub.ReadElementContentAsDecimal(); break;
}
}
yield return new Product(id, name, price);
}
}
record Product(int Id, string Name, decimal Price);
The ReadProducts method yields one Product at a time.
using XmlReader sub = reader.ReadSubtree();
ReadSubtree returns a reader that sees only the current element and its
descendants. The inner loop cannot run past the end of the product, even if an
optional element is missing. When the subtree reader is closed, the outer reader is
positioned on the end tag of the element.
yield return new Product(id, name, price);
The caller receives each product as soon as it has been read. Only one product is
in memory at any time, so a gigabyte file can be processed with a few kilobytes
of memory, and the caller can stop early with break or LINQ's
Take.
$ dotnet run
Product { Id = 1, Name = Product A, Price = 1200 }
Product { Id = 2, Name = Product B, Price = 1100 }
C# XmlReader async
When the XML comes from a network stream or a slow disk, we can read it
asynchronously. The settings must have Async set to
true; otherwise the asynchronous methods throw.
using System.Xml;
var settings = new XmlReaderSettings { Async = true, IgnoreWhitespace = true };
using var stream = File.OpenRead("books.xml");
using var reader = XmlReader.Create(stream, settings);
while (await reader.ReadAsync())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "title")
{
string title = await reader.ReadElementContentAsStringAsync();
Console.WriteLine(title);
}
}
The example prints the titles of the books asynchronously.
var settings = new XmlReaderSettings { Async = true, IgnoreWhitespace = true };
The Async property enables asynchronous reading.
while (await reader.ReadAsync())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "title")
{
string title = await reader.ReadElementContentAsStringAsync();
Console.WriteLine(title);
}
}
Each synchronous method has an asynchronous counterpart with the
Async suffix.
$ dotnet run Dune Old Goriot
C# XmlReader Skip and errors
The Skip method skips the current node and all its children. Care is
needed when mixing it with the ReadElementContentAs... methods, which
already advance the reader; calling Read after them would skip the node
that follows.
using System.Xml;
string xml = "<root><a>1</a><b>2</b><a>3</a></root>";
using var reader = XmlReader.Create(new StringReader(xml));
reader.MoveToContent();
reader.Read(); // move inside <root>
int sum = 0;
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "a")
{
sum += reader.ReadElementContentAsInt(); // consumes </a> and moves on
}
else
{
reader.Skip(); // skips this node and all its children
}
}
Console.WriteLine(sum);
// malformed XML throws as soon as the problem is read
try
{
using var bad = XmlReader.Create(new StringReader("<a><b></a>"));
while (bad.Read()) { }
}
catch (XmlException e)
{
Console.WriteLine($"line {e.LineNumber}, position {e.LinePosition}: {e.Message}");
}
The program sums the values of the a elements and ignores the others.
Then it reads malformed XML.
sum += reader.ReadElementContentAsInt();
The method consumes the whole element, so the loop must not call
Read in this branch.
catch (XmlException e)
{
Console.WriteLine($"line {e.LineNumber}, position {e.LinePosition}: {e.Message}");
}
Malformed XML throws an XmlException when the reader reaches the
problem, and not earlier. The exception reports the line and position. Because the
reader is forward-only, nodes that were read before the error have already been
processed, so code that needs all-or-nothing behaviour must validate first or
collect the results and use them only at the end.
$ dotnet run 4 line 1, position 9: The 'b' start tag on line 1 position 5 does not match the end tag of 'a'. Line 1, position 9.
C# XmlReader namespaces
XML namespaces qualify element and attribute names. The reader exposes the
LocalName, NamespaceURI, and Prefix of every
node, and several methods accept a namespace URI, so our code does not depend on the
prefixes used in the document.
using System.Xml;
string xml = """
<feed xmlns="http://example.com/feed" xmlns:m="http://example.com/meta">
<entry m:lang="en">hello</entry>
<entry m:lang="sk">ahoj</entry>
</feed>
""";
using var reader = XmlReader.Create(new StringReader(xml));
while (reader.ReadToFollowing("entry", "http://example.com/feed"))
{
string? lang = reader.GetAttribute("lang", "http://example.com/meta");
Console.WriteLine($"{reader.LocalName} ({reader.NamespaceURI}) lang={lang}: {reader.ReadElementContentAsString()}");
}
The document uses a default namespace for the elements and a prefixed namespace for an attribute.
while (reader.ReadToFollowing("entry", "http://example.com/feed"))
{
string? lang = reader.GetAttribute("lang", "http://example.com/meta");
We look for the entry element in the given namespace, and for the
lang attribute in the other one. An unprefixed attribute has no
namespace, and an unprefixed element takes the default namespace.
$ dotnet run entry (http://example.com/feed) lang=en: hello entry (http://example.com/feed) lang=sk: ahoj
Source
In this article we have read XML data with XmlReader.
Author
List all C# tutorials.