Skip to content

Suggestions for Improving Native AOT Support #1085

Closed

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: DeserializerBuilder → StaticDeserializerBuilder
  2. Table of supported/unsupported builder methods
  3. Known limitations and workarounds
  4. Checklist of common issues (required members, generic collections, BCL type converters)

Activity

  1. fdcastel commented on Mar 26, 2026

    @fdcastel
    ContributorAuthor

    I’ve just ported a project that uses YamlDotNet to AOT.

    For anyone interested in doing the same, I’ve put together a document outlining the migration process here

    The main issue I’m facing now is the absence of the YamlDotNet.Analyzers.StaticGenerator package -- I currently have to download the source and build it manually to use it.

  2. EdwardCooke commented on Mar 27, 2026

    @EdwardCooke
    Collaborator
    1. It is on nuget under mine. It's under Vecc.YamlDotNet.Analyzers.StaticGenerator. Everytime I cut a release I also release that package as well. I've been waiting for @aaubry to scope the nuget api key to allow me to push up an official package, thats been a couple of years now. I believe I called this out in the wiki. It's somewhere, I know that.
    2. If you're up for implementing that then it would be greatly appreciated.
    3. The project .net version is only up to version 8 right now so .net 9+ types are not supported yet.

    A. That is by design. If we were to implement all of the types (or even pick and choose) then the resulting binary from AoT would be larger than is needed. A lot of types would be included that don't need to be, thus, starting to undermine some of the benefits of AoT as a whole. It also starts running the risk of slowing down the AoT implementation with more if statements (maybe, I'd have to think about that more)
    B. If you want to put in IParsable code, that would be a very good solution and I would appreciate that being done
    5. Agreed. I would welcome that change
    6. That would be useful, I would approve of that
    7. Looks like you created a document for it already. I'll take a look at it in a bit and see if I can incorporate it into the wiki.

  3. fdcastel commented on Mar 28, 2026

    @fdcastel
    ContributorAuthor

    @EdwardCooke, thanks so much for the insights and explanations. Really appreciate it!

    Some of these items are already addressed in #1092. I’ll go ahead and work on the remaining ones now, but feel free to let me know if you’d prefer I split #1092 into smaller, individual PRs.

  4. fdcastel commented on Mar 28, 2026

    @fdcastel
    ContributorAuthor

    All items now addressed in #1092.

  5. added a commit that references this issue on Apr 9, 2026
    f0ccfd8
  6. aaubry commented on Apr 10, 2026

    @aaubry
    Owner

    A fix for this issue has been released in version 17.0.0.

  7. aaubry commented on Apr 10, 2026

    @aaubry
    Owner

    A fix for this issue has been released in version 17.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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