LightXmlWriter 1.1.0

dotnet add package LightXmlWriter --version 1.1.0
NuGet\Install-Package LightXmlWriter -Version 1.1.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LightXmlWriter" Version="1.1.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add LightXmlWriter --version 1.1.0
#r "nuget: LightXmlWriter, 1.1.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install LightXmlWriter as a Cake Addin
#addin nuget:?package=LightXmlWriter&version=1.1.0

// Install LightXmlWriter as a Cake Tool
#tool nuget:?package=LightXmlWriter&version=1.1.0

LightXmlWriter

This is a replacement of XmlWriter which can be useful in high-performance scenarios.

There are several goals to reach:

  • high performance
    • zero allocation
    • be faster than XmlWriter
    • no runtime validation
    • keep it simple (no configuration)
    • allow writing concrete types (Span<T>, int, double, DateTime, etc.)
    • allow to disable escaping values in similar way to Newtonsoft.Json
    • produce as small as possible output XML
  • easy migration from XmlWriter

LightXmlWriter vs XmlWriter

Benchmarks

OTA_Standard_XML.LightXmlWriter_Write_Xml

|         Method |      Mean |     Error |    StdDev |  Median | Allocated |
|--------------- |----------:|----------:|----------:|--------:|----------:|
| LightXmlWriter |  8.585 us | 0.1733 us | 0.2429 us |  8.6 us |         - |
|      XmlWriter | 17.923 us | 0.4765 us | 1.3671 us | 18.3 us |    1288 B |

Similarities

  • Method names are kept the same, and they should work the same way

For example for both writers:

writer.WriteStartElement("soapenv", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");

produce:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  • both are not thread-safe

Both writers keep state, so they must not be processed in parallel. LightXmlWriter does not do any validation, so it could produce an unexpeced output.

  • both implement these methods:

    • WriteStartElement
    • WriteEndElement
    • WriteElementString
    • WriteStartAttribute
    • WriteEndAttribute
    • WriteAttributeString
    • WriteRaw
    • WriteValue
  • both implement IDisposable interface

Differences

  • LightXmlWriter contains more overloads of methods

The overloads accept int, double, bool, ReadOnlySpan<char> (only in .NET Core), char[] etc. You can even use overload that uses Action on TextWriter.

  • WriteEndElement must have name of the element

XmlWriter:

writer.WriteStartElement("Person");
writer.WriteEndElement();

LightXmlWriter:

writer.WriteStartElement("Person");
writer.WriteEndElement("Person");
  • LightXmlWriter does not use XmlWriterSettings

It only uses the default settings. That means, no validation at run-time, no pretty-print of output, no XML declaration - it is just simple writer.

  • LightXmlWriter has better performance than XmlWriter

LightXmlWriter is about 2x faster and generates less allocations than XmlWriter because of methods overloads that don't need conversion to string before.

  • LightXmlWriter produces more compressed self-closed xml tags

The compressed version does not contain space before closing singn. It is still valid XML.

XmlWriter:

writer.WriteStartElement("Person");
writer.WriteEndElement();
//produces <Person />

LightXmlWriter:

writer.WriteStartElement("Person");
writer.WriteEndElement("Person");
//produces <Person/>
  • LightXmlWriter does not validate & escape tag and attribute names

XmlReader throws an exception in case of unescaped name.

  • LightXmlWriter allows write values without escaping

This is similar to Newtonsoft JsonWriter where you can set flag escape: false. It brings better performance than with enabled escape. Use it where you are sure that value does not need escaping.

Example:

writer.WriteStartElement("Code");
writer.WriteValue("ABCD", escape: false);
writer.WriteEndElement();
//produces <Code>ABCD</Code>

writer.WriteElementString("Code", "ABCD", escapeValue: false);
//produces <Code>ABCD</Code>

Migration from XmlWriter

  1. To each WriteEndElement(); add argument with name of closing element, e.g. WriteEndElement("Person");
  2. Optionally, to WriteEndAttribute(); add argument with name of closing attribute, e.g. WriteEndAttribute("Age");
  3. Optionally, use more adequate method overload to reduce string allocations, e.g. WriteAttributeString("Age", 25.ToString()); to WriteAttributeString("Age", 25);
  4. Optionally. disable value escaping where it is not needed, e.g. WriteAttributeString("Code", "ABC123"); to WriteAttributeString("Code", "ABC123", escapeValue: false);
  5. If you see any difference in output, report it here

Contributing

You are welcome to help with this package. There are also a lot to do: write more benchmark tests, more tests, more overloads, finding bugs, making optimisations of existing code.

Acknowledgments

Please use it carefully. LightXmlWriter can produce an invalid XML, so write tests for each output.

Requirements

Framework compatible with .NET Standard 1.3 (.NET Core 1.0, .NET Framework 4.6, Mono 4.6) or higher.
Currently ReadOnlySpan<char> overloads are only available in .NET Core 2.1 and .NET 5.0 builds.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp1.0 was computed.  netcoreapp1.1 was computed.  netcoreapp2.0 was computed.  netcoreapp2.1 is compatible.  netcoreapp2.2 is compatible.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard1.3 is compatible.  netstandard1.4 was computed.  netstandard1.5 was computed.  netstandard1.6 was computed.  netstandard2.0 was computed.  netstandard2.1 is compatible. 
.NET Framework net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen30 was computed.  tizen40 was computed.  tizen60 was computed. 
Universal Windows Platform uap was computed.  uap10.0 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.0 3,209 1/13/2023
1.0.2 1,712 7/10/2021
1.0.1 292 6/8/2021
1.0.0 445 5/22/2021

- Added ReadOnlySpan API to netstandard1.3 build