Skip to content

Suggestions for Improving Native AOT Support #1085

Description

@fdcastel

These suggestions are based on hands-on experience migrating a real-world .NET 10 application (ASP.NET Core + SignalR + YamlDotNet) to Native AOT using StaticDeserializerBuilder and YamlDotNet.Analyzers.StaticGenerator.

I’m happy to work on PRs for each of these, pending approval from the YamlDotNet team.

These are just initial suggestions. I’ll create a separate issue for each one that gets approved for work.


1. Publish the Source Generator as a NuGet Package

Problem: YamlDotNet.Analyzers.StaticGenerator is not published to NuGet. Users must build it from source or reference it as a local project.

Impact: This is the single biggest barrier to AOT adoption. Users who want [YamlStaticContext] must clone the YamlDotNet repo and add a <ProjectReference ... OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> to their project. Most users will never discover this workflow.

Suggestion: Publish the analyzer as a NuGet package (as the .csproj already has PackageId and packaging metadata configured), or bundle it inside the main YamlDotNet package under the analyzers/ folder — similar to how System.Text.Json ships its source generator inside the main package.


2. Support required Members in Generated Object Factories

Problem: The source generator emits new T() in StaticObjectFactory.Create(), which fails to compile when T has C# required members (error CS9035).

Impact: The required keyword is idiomatic modern C# (since C# 11) and commonly paired with [Required] from DataAnnotations for validation. Users are forced to remove required and use = null!; defaults, weakening their type contracts.

Suggestion: Detect required properties during source generation and emit object initializer syntax instead of bare new T():

// Current generated code (fails with CS9035):
case "MyType": return new MyType();

// Suggested:
case "MyType": return new MyType() { RequiredProp = default! };
// Or use [SetsRequiredMembers] on generated factory methods

3. Support OrderedDictionary<TKey, TValue> in the Static Context

Problem: The source generator does not handle System.Collections.Generic.OrderedDictionary<TKey, TValue> (introduced in .NET 9). When registered via [YamlSerializable(typeof(OrderedDictionary<string, T>))], the generated code emits the unqualified type name OrderedDictionary without generic arguments, causing error CS0305.

Impact: YAML documents are inherently ordered. OrderedDictionary<K,V> is the natural .NET type for representing ordered YAML mappings. Users must downgrade to Dictionary<K,V> (which only preserves insertion order as an implementation detail, not a contract).

Suggestion: Update the source generator's type name emission to properly handle generic types, including newer BCL types like OrderedDictionary<TKey, TValue>.


4. Built-in Type Converters for Common BCL Types

Problem: StaticDeserializerBuilder does not automatically convert YAML scalars to TimeSpan, IPEndPoint, Uri, or HttpMethod. The reflection-based DeserializerBuilder handles these via ScalarNodeDeserializer using TypeConverter infrastructure, but the static builder lacks these code paths.

Impact: Users must write and register custom IYamlTypeConverter implementations for every non-primitive type. This is boilerplate-heavy and error-prone. A fresh migration required creating TimeSpanConverter, IPEndPointConverter, and HttpMethodConverter — types that "just worked" with the reflection-based deserializer.

Suggestion: Either:

  • Ship built-in IYamlTypeConverter implementations for common BCL types (TimeSpan, DateTimeOffset, DateTime, Uri, IPAddress, IPEndPoint, Guid, Version, HttpMethod, TimeOnly, DateOnly), or
  • Have the source generator emit conversion code for types that implement IParsable<T> or have a TypeConverter, mirroring what ScalarNodeDeserializer does at runtime.

5. Better Error Messages for Unregistered Types

Problem: When a type is not registered in the [YamlStaticContext], the runtime error is:

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
(Parameter 'Unknown type: System.Collections.Generic.OrderedDictionary`2[...]')

Impact: The ArgumentOutOfRangeException type is misleading — it suggests a value range issue rather than a missing type registration. Users unfamiliar with the static context pattern will struggle to diagnose this.

Suggestion: Throw a more descriptive exception, e.g.:

throw new InvalidOperationException(
    $"Type '{type.FullName}' is not registered in the YamlDotNet static context. " +
    $"Add [YamlSerializable(typeof({type.Name}))] to your static context class.");

6. Roslyn Analyzer Diagnostic for Missing Type Registrations

Problem: If a type used in a [YamlSerializable] class hierarchy is not itself registered in the static context, the error only surfaces at runtime.

Impact: Users discover missing registrations only when the application crashes during deserialization. This defeats the purpose of compile-time source generation.

Suggestion: Add a Roslyn analyzer diagnostic that:

  • Walks all registered types' properties recursively
  • Emits a warning/error for any property type (or generic argument) that is not also registered in the context
  • Example: if ApplicationConfiguration has a Dictionary<string, PullMonitorConfiguration> property, and Dictionary<string, PullMonitorConfiguration> is not registered, emit a diagnostic at compile time.

7. Document the StaticDeserializerBuilder API Parity Gaps

Problem: The migration from DeserializerBuilder to StaticDeserializerBuilder is not documented. Users discover API differences (missing methods, behavioral changes) only through trial and error.

Impact: The migration path is unclear. The README and wiki focus on the [YamlStaticContext] attribute but don't explain:

  • Which DeserializerBuilder methods are available on StaticDeserializerBuilder
  • Which BCL type conversions are lost
  • That required properties break the generated code
  • That OrderedDictionary<K,V> is unsupported

Suggestion: Add a migration guide section in the documentation, structured as:

  1. Step-by-step: DeserializerBuilderStaticDeserializerBuilder
  2. Table of supported/unsupported builder methods
  3. Known limitations and workarounds
  4. Checklist of common issues (required members, generic collections, BCL type converters)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions