Source-Generated XML and Protobuf in C
Source-generated XML and Protobuf serialization in modern C# replaces traditional runtime reflection with compile-time code generation to transform objects into serialized formats. This approach drastically improves application startup times, eliminates memory overhead associated with reflection, and ensures full compatibility with Native Ahead-Of-Time (AOT) compilation in modern .NET runtimes. This article covers how source generators handle XML and Protocol Buffers (Protobuf), why they outperform traditional serialization mechanisms, and their primary benefits in modern, high-performance systems.
The Problem with Traditional Serialization
Historically, serializers such as
System.Xml.Serialization.XmlSerializer and early versions
of protobuf-net relied heavily on runtime reflection and
runtime code emission via Reflection.Emit. At startup, these libraries
inspect an object's properties, types, and custom attributes to
dynamically construct serialization logic.
While flexible, this approach introduces distinct drawbacks:
- Startup Latency: Dynamically generating serialization logic at startup increases cold-start times.
- High Memory Usage: Caching metadata and generated dynamic assemblies consumes non-trivial amounts of memory.
- Incompatibility with Native AOT: Modern .NET platforms targeting Native AOT cannot use dynamic code generation or heavy reflection because unused code is stripped during linking, causing runtime failures.
How Source-Generated Serialization Works
Introduced in .NET 5 and expanded in subsequent releases, Roslyn Source Generators allow developers to inspect C# code during compilation and generate additional C# source files directly into the build pipeline.
When applied to serialization:
- Inspection: The source generator scans data contract classes decorated with specific attributes (such as XML attributes or Protobuf tags).
- Code Generation: Instead of deferring logic to runtime, the generator produces concrete, strongly typed C# methods that read directly from or write directly to format-specific streams and buffers.
- Compilation: The emitted serialization code compiles alongside your application logic into the final binary.
Source-Generated Protobuf
In the Protobuf ecosystem, tools like protobuf-net
provide source generators, and Google's official protoc
compiler generates standard C# contracts. Modern C# protobuf source
generators inspect POCOs decorated with [ProtoContract] and
[ProtoMember] attributes to emit direct binary encoding and
decoding routines. This translates to direct writes into
IBufferWriter<byte> or Span<byte>,
avoiding boxing and runtime type inspection.
Source-Generated XML
While .NET introduced the built-in System.Text.Json
source generator in .NET 6, XML serialization in modern high-throughput
architectures has historically relied on external tooling or
ahead-of-time tools like sgen.exe. Modern source generators
for XML analyze types marked with [XmlRoot] and
[XmlElement], generating custom XmlReader and
XmlWriter implementations directly. The generated code
explicitly advances tokens and writes nodes without checking metadata at
runtime.
Key Benefits in Modern C# Systems
1. Native AOT Compatibility
Native AOT requires all executable code to exist before compilation finishes. Source-generated serializers produce standard, static C# code, enabling applications to run under Native AOT without missing member exceptions or requiring complex trimming configuration files.
2. Reduced Latency and Allocation
Generated serializers call strongly typed properties directly. This
eliminates boxing of value types, bypasses dynamic dispatch, and
utilizes low-allocation APIs such as ReadOnlySpan<T>
and Memory<T>.
3. Compile-Time Diagnostics
Because logic is evaluated during compilation, invalid contract designs—such as duplicate Protobuf tags, non-serializable property types, or missing parameterless constructors—are surfaced as compiler errors or warnings instead of failing silently until a request is handled in production.
Conclusion
Source-generated XML and Protobuf serialization modernize C# data handling by moving serialization work from runtime to compile time. By shifting to source-generated serialization, modern .NET services achieve faster cold starts, lower allocations, and full readiness for Native AOT deployment.