---
title: "YAML in a C# native AOT app"
url: https://recursiveai.org/oneyaml/articles/csharp-yaml-native-aot/
description: "YAML in a C# native AOT app: OneYaml’s built-in source generator, the trim warnings you get without it, and how YamlDotNet and SharpYaml do it."
date: 2026-10-26
author: oneyaml team
---

# YAML in a C# native AOT app

YAML in a C# native AOT app: OneYaml’s built-in source generator, the trim warnings you get without it, and how YamlDotNet and SharpYaml do it.

October 26, 2026 · oneyaml team

To use YAML in a C# native AOT app, bind it through source-generated metadata instead of reflection. Native AOT trims away the members that a reflection-based serializer reads at run time, so a plain `Deserialize<T>(yaml)` call warns at publish time and can fail once published. With OneYaml you declare a `YamlSerializerContext`, list your root types with `[YamlSerializable]`, and pass the generated metadata to `YamlSerializer`. The generator ships inside the `OneYaml` package, the pattern is the one System.Text.Json uses, and `YamlDocument` edits that keep comments publish without warnings too. The code on this page uses OneYaml 1.2.1 on .NET 10 and was published with native AOT for linux-x64.

## 1. Turn on native AOT and add the package

*shell*

```
dotnet new console -f net10.0
dotnet add package OneYaml --version 1.2.1
```

*MyApp.csproj · excerpt*

```
<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <PublishAot>true</PublishAot>
</PropertyGroup>
```

OneYaml targets .NET 8 and .NET 10, the two runtimes it supports ([stability](https://recursiveai.org/oneyaml/csharp/docs/stability/)). There is no second package to add for the generator.

## 2. Declare a context and pass its metadata

*Program.cs · OneYaml 1.2.1 · .NET 10*

```
using OneYaml;
 
string yaml = """
    name: billing-api
    replicas: 3
    endpoint: https://billing.internal.example
    labels:
      tier: backend
      team: payments
    """;
 
ServiceConfig config = YamlSerializer.Deserialize(yaml, AppYamlContext.Default.ServiceConfig)!;
Console.WriteLine($"{config.Name}: {config.Replicas} replicas at {config.Endpoint.Host}");
Console.WriteLine($"labels: {string.Join(", ", config.Labels.Select(l => $"{l.Key}={l.Value}"))}");
 
Console.Write(YamlSerializer.Serialize(config with { Replicas = 5 }, AppYamlContext.Default.ServiceConfig));
 
record ServiceConfig(string Name, int Replicas, Uri Endpoint, Dictionary<string, string> Labels);
 
[YamlSerializable(typeof(ServiceConfig))]
partial class AppYamlContext : YamlSerializerContext;
```

*Output*

```
billing-api: 3 replicas at billing.internal.example
labels: tier=backend, team=payments
name: billing-api
replicas: 5
endpoint: https://billing.internal.example
labels:
  tier: backend
  team: payments
```

The generator writes a `YamlTypeInfo<T>` property on the context for each `[YamlSerializable]` type and for every type it reaches, so `Dictionary<string, string>` and `Uri` need no attribute of their own. Records with constructor parameters bind as they do with reflection, and so do the System.Text.Json attributes, converters and polymorphism ([serializer](https://recursiveai.org/oneyaml/csharp/docs/serializer/)). For options other than the defaults, such as a naming policy, create the context with them: `new AppYamlContext(new YamlSerializerOptions { ... })`.

## 3. Publish

*shell*

```
dotnet publish -c Release -r linux-x64
```

The publish finished with no trim or AOT warnings, and the output is one native executable that prints the same lines as above. On linux-x64 with .NET SDK 10.0.400 it was 2,377,208 bytes, plus a separate `.dbg` symbols file. Your size will differ with your code and target.

## What happens if you call the reflection overload

The overloads that take `YamlSerializerOptions` use reflection, and OneYaml marks them `[RequiresUnreferencedCode]` and `[RequiresDynamicCode]`, so the compiler tells you before anything breaks. Here is the same app written the reflection way:

*Program.cs · OneYaml 1.2.1 · .NET 10*

```
using OneYaml;
 
var config = YamlSerializer.Deserialize<ServiceConfig>("name: billing-api\nreplicas: 3\n");
Console.WriteLine($"{config!.Name}: {config.Replicas} replicas");
 
record ServiceConfig(string Name, int Replicas);
```

It runs under `dotnet run`, but `dotnet publish` reports both warnings (shown here without the directory prefix and the project-file suffix):

*dotnet publish · warnings*

```
Program.cs(3,14): warning IL2026: Using member 'OneYaml.YamlSerializer.Deserialize<T>(String, YamlSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Reflection-based binding reads members and constructors that trimming can remove. Pass a YamlTypeInfo from a YamlSerializerContext instead.
Program.cs(3,14): warning IL3050: Using member 'OneYaml.YamlSerializer.Deserialize<T>(String, YamlSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. Reflection-based binding makes generic types and compiles accessors at run time. Pass a YamlTypeInfo from a YamlSerializerContext instead.
```

Run the published binary and the call fails at once, with a message that says what to do, rather than returning an object with missing members:

*native binary · stderr*

```
Unhandled exception. System.InvalidOperationException: Reflection-based binding is turned off in this application. Pass generated metadata from a YamlSerializerContext, set TypeInfoResolver, or set the OneYaml.YamlSerializer.IsReflectionEnabledByDefault switch to true.
```

Reflection-based binding is off by default in apps published with `PublishTrimmed` or `PublishAot`. The MSBuild property `YamlSerializerIsReflectionEnabledByDefault` turns it back on, but only do that if you have kept the members it needs. Mistakes in a context, such as a context that isn’t `partial`, are build errors `OY0001` to `OY0010` ([serializer](https://recursiveai.org/oneyaml/csharp/docs/serializer/)).

## Edit YAML files in an AOT app

Many AOT apps are command-line tools, and tools often change a config file rather than just read it. `YamlDocument` changes the values you set and keeps every comment and the rest of the layout. Setting strings and numbers needs no context at all:

*Program.cs · OneYaml 1.2.1 · .NET 10*

```
using OneYaml;
 
string yaml = """
    # Deployed by the release job
    image:
      repository: registry.example.com/billing-api
      tag: 1.4.2   # bump on release
    replicas: 3
    """;
 
using var doc = YamlDocument.Parse(yaml);
doc.Set("/image/tag", "1.5.0");
doc.Root["replicas"] = 5;
Console.WriteLine(doc);
 
string tag = doc.Root["image"]!["tag"]!.GetValue<string>();
Console.WriteLine($"tag is now {tag}");
```

*Output*

```
# Deployed by the release job
image:
  repository: registry.example.com/billing-api
  tag: 1.5.0   # bump on release
replicas: 5
tag is now 1.5.0
```

This app also published for native AOT with no warnings, and the binary prints the output above. To write a whole object into a document, `YamlDocument` has generated-metadata overloads: `Set(path, value, typeInfo)`, `Get(path, typeInfo)` and `YamlNode.Deserialize(typeInfo)` ([editing](https://recursiveai.org/oneyaml/csharp/docs/editing/)).

## YamlDotNet and SharpYaml with native AOT

| Library | Generator | How you declare it | Source |
|---|---|---|---|
| OneYaml 1.2.1 | In the `OneYaml` package | `partial class : YamlSerializerContext` with `[YamlSerializable]`; pass `Context.Default.T` | [serializer docs](https://recursiveai.org/oneyaml/csharp/docs/serializer/); publish measured above |
| YamlDotNet 18.1.0 | Separate package, [Vecc.YamlDotNet.Analyzers.StaticGenerator](https://www.nuget.org/packages/Vecc.YamlDotNet.Analyzers.StaticGenerator) 18.1.0, “a static context for use with YamlDotNet to support ahead-of-time and library trimming” | `partial class : StaticContext` with `[YamlStaticContext]` and `[YamlSerializable]`; build with `StaticDeserializerBuilder` | NuGet; [issue #740](https://github.com/aaubry/YamlDotNet/issues/740) (closed as completed, March 2023); publish measured below |
| SharpYaml 3.16.0 | In the `SharpYaml` package (“no extra package needed”) | `partial class : YamlSerializerContext` with `[YamlSerializable]`; reflection is off by default under `PublishAot` | [README](https://github.com/xoofx/SharpYaml#readme), [source generation](https://github.com/xoofx/SharpYaml/blob/main/site/docs/source-generation.md) |

YamlDotNet’s wiki doesn’t cover native AOT, so this sample uses the public types of the two published packages. It ran, and it published for native AOT on linux-x64 with no warnings:

*Program.cs · YamlDotNet 18.1.0 + StaticGenerator 18.1.0*

```
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
 
string yaml = """
    name: billing-api
    replicas: 3
    labels:
      tier: backend
      team: payments
    """;
 
IDeserializer deserializer = new StaticDeserializerBuilder(new AppStaticContext())
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();
ServiceConfig config = deserializer.Deserialize<ServiceConfig>(yaml);
Console.WriteLine($"{config.Name}: {config.Replicas} replicas, {config.Labels.Count} labels");
 
public class ServiceConfig
{
    public string Name { get; set; } = "";
    public int Replicas { get; set; }
    public Dictionary<string, string> Labels { get; set; } = new();
}
 
[YamlStaticContext]
[YamlSerializable(typeof(ServiceConfig))]
public partial class AppStaticContext : StaticContext
{
}
```

*Output*

```
billing-api: 3 replicas, 2 labels
```

If you are moving an AOT app from YamlDotNet, note that the `OneYaml.Compat.YamlDotNet` package doesn’t cover YamlDotNet’s static contexts. Switch those call sites to a `YamlSerializerContext` as shown above. [Replacing YamlDotNet](https://recursiveai.org/oneyaml/articles/yamldotnet-alternative/) covers the rest of a migration.

## Where to go next

The short version of this page is the cookbook recipe [use YAML in a native AOT app](https://recursiveai.org/oneyaml/csharp/cookbook/native-aot/). To pick a library first, see [choosing a C# YAML library](https://recursiveai.org/oneyaml/articles/csharp-yaml-parser/); to change files without losing comments, [edit YAML without losing comments](https://recursiveai.org/oneyaml/articles/edit-yaml-keep-comments/). Install and first steps: [getting started with OneYaml](https://recursiveai.org/oneyaml/csharp/docs/getting-started/). Versions on this page are the latest published on 26 October 2026.
