Was ist .NET Native AOT? - Der Unterschied zu JIT und Trimming

· · C#, .NET, Native AOT, Veröffentlichung, Design

Im Artikel C# Native AOT DLL aus C/C++ aufrufen haben wir bereits beschrieben, wie man mit Native AOT C# aus C/C++ heraus aufruft. Eigentlich wäre es aber freundlicher gewesen, zuerst zu klären, was Native AOT überhaupt ist. Die Reihenfolge ist hier ein wenig vertauscht.

Beim Thema Native AOT vermischen sich zunächst vor allem die Begriffe.

  • Geht es darum, den JIT loszuwerden?
  • Was unterscheidet es von self-contained und single-file?
  • Gehört es zur selben Familie wie ReadyToRun?
  • Was passiert eigentlich, wenn massenhaft Trimming-Warnungen auftreten?
  • Lässt es sich bei WPF / WinForms / ASP.NET Core mit derselben Gelassenheit einsetzen?

Vermischt sich das, wirkt Native AOT entweder wie „reine Magie, die alles schneller macht“ oder umgekehrt wie „etwas Angsteinflößendes voller Einschränkungen“. Beide Sichtweisen sind etwas ungenau.

Dieser Artikel geht vor allem vom praktischen Stand ab .NET 8 aus und klärt zuerst diese vier Punkte.

  • Was Native AOT tatsächlich ist
  • Was es bringt und wo es schwierig wird
  • Wie es sich von ReadyToRun und Trimming unterscheidet
  • Mit welcher Art von Anwendung ein sanfter Einstieg gelingt

Inhaltsverzeichnis

  1. Zuerst das Fazit (in einem Satz)
  2. Die erste Übersichtstabelle
    • 2.1. Begriffe rund um Native AOT
    • 2.2. Der Unterschied zwischen JIT / ReadyToRun / Native AOT
  3. Das Gesamtbild von Native AOT (Diagramm)
  4. Was Native AOT bringt
    • 4.1. Der Start wird tendenziell leichter
    • 4.2. Keine vorinstallierte Runtime nötig
    • 4.3. Passt zu eingeschränkten Ausführungsumgebungen
  5. Wo Native AOT schwierig wird
    • 5.1. Reflection und dynamische Codeerzeugung
    • 5.2. Man muss von Trimming ausgehend denken
    • 5.3. Veröffentlichung pro Plattform
    • 5.4. Bei Windows-Desktop / COM ist besondere Vorsicht geboten
  6. Minimaler Ablauf
    • 6.1. csproj
    • 6.2. Publish
    • 6.3. Umgang mit JSON
  7. Geeignete Fälle
  8. Ungeeignete Fälle
  9. Stolperfallen
  10. Zusammenfassung
  11. Quellen

1. Zuerst das Fazit (in einem Satz)

  • Native AOT ist ein Modell, das eine .NET-Anwendung beim Publish vorab in nativen Code kompiliert und in dieser Form ausliefert.
  • Da kein Laufzeit-JIT verwendet wird, verbessern sich Startzeit und Speicherbedarf tendenziell, und die Anwendung lässt sich leicht auf Umgebungen ohne installierte .NET-Runtime verteilen.
  • Im Gegenzug verträgt es sich schlecht mit freizügiger Reflection, dynamischer Codeerzeugung, built-in COM und Bibliotheken ohne Trimming-Unterstützung.
  • Mit anderen Worten: Es ist weniger ein Zaubertrick für mehr Geschwindigkeit als vielmehr ein Veröffentlichungsmodell, das zugunsten von Start, Verteilung und Ausführungsumgebung einen Teil der dynamischen Welt aufgibt und sich der statischen Welt annähert.

Native AOT ist „ein Mechanismus, um .NET wie eine native Anwendung auszuliefern“ und keine einfache Checkbox für schnelleres Kompilieren.

2. Die erste Übersichtstabelle

2.1. Begriffe rund um Native AOT

Zunächst lohnt es sich, diese Begriffe zu trennen – das macht alles Folgende leichter.

Begriff Was er tut Verhältnis zu Native AOT
JIT Erzeugt zur Laufzeit nativen Code aus IL Native AOT erledigt das vorab
self-contained Liefert das benötigte .NET-Paket zusammen mit der Anwendung aus Native AOT gehört gedanklich zu dieser Familie
single-file Bündelt das Verteilungsergebnis in einer Datei Vom Wesen von Native AOT getrennt, aber das Ergebnis sieht oft ähnlich aus
trimming Entfernt ungenutzten Code Bei Native AOT praktisch Voraussetzung
ReadyToRun Behält die IL bei, nimmt aber einen Teil der JIT-Arbeit vorweg Klingt ähnlich, ist aber etwas grundlegend anderes
Source Generator Verlagert dynamisches Verhalten zur Laufzeit in Codeerzeugung zur Build-Zeit Passt gut zu Native AOT

Verwirrend ist, dass Native AOT weniger ein einzelnes Feature als vielmehr ein Veröffentlichungsmodell ist, das eng mit self-contained, Trimming, Source Generation und RID-fixiertem Publish zusammenspielt.

2.2. Der Unterschied zwischen JIT / ReadyToRun / Native AOT

Auch das lässt sich am schnellsten anhand einer einzigen Tabelle erfassen.

Aspekt Gewöhnliche JIT-Ausführung ReadyToRun Native AOT
Laufzeit-JIT Wird verwendet Kommt in manchen Fällen noch zum Einsatz Wird nicht verwendet
Inhalt des Verteilungsergebnisses Überwiegend IL IL + vorab erzeugter Code Überwiegend eine native ausführbare Datei
Start Referenzwert Lässt sich leicht verbessern Lässt sich deutlich verbessern
Kompatibilität Am breitesten Breit Stark eingeschränkt
Dynamische Funktionen Einfach nutzbar Größtenteils einfach nutzbar Stark eingeschränkt
Geeigneter Zweck Gewöhnliche .NET-Entwicklung allgemein Erster Schritt zur Startverbesserung Konsequente Ausrichtung auf Start, Verteilung und eingeschränkte Umgebungen

Wenn ReadyToRun die Richtung „dem JIT etwas Arbeit abnehmen“ verfolgt, geht Native AOT in Richtung „den Laufzeit-JIT selbst nicht voraussetzen“. Auch wenn beide dasselbe Wort AOT tragen, ist die Grundstimmung ziemlich unterschiedlich.

Fasst man daraufhin „welche Verteilungsform am Ende gewählt wird“ in einem Bild zusammen, ergibt sich Folgendes.

Ja, gehtNicht unbedingtJa, verkürzenWill/kann nicht installiert werdenJa, abhängigNein, unabhängigJaNeinVerteilungsform festlegenKann am Zielort eine .NET-Runtime installiert werden?Soll die Startzeit verkürzt werden?framework-dependentgewöhnliche VeröffentlichungReadyToRunStartverbesserung bei erhaltener KompatibilitätBestehen Abhängigkeiten von Reflection, dynamischer Codeerzeugung oder built-in COM?self-containedbei Bedarf mit single-file bündelnIst das Ziel Konsole / Worker / kleine API?Native AOTzunächst self-containeddynamische Abhängigkeiten reduzieren und neu bewerten

Die erste Verzweigung betrifft ob sich eine Runtime beim Verteilungsziel unterbringen lässt, die zweite wie weit sich dynamische Mechanismen reduzieren lassen. self-contained und single-file sind eine Frage der „Art der Verteilung“, ReadyToRun und Native AOT eine Frage „wann nativer Code entsteht“ – in der Praxis werden diese Aspekte kombiniert betrachtet.

3. Das Gesamtbild von Native AOT (Diagramm)

Grob skizziert sieht Native AOT so aus.

normale Ausführungdotnet publish + PublishAotC# / .NET-QuellcodeIL-AssemblysLaufzeit-JITAnwendung läuftAOT-/Trim-AnalyseEntfernen ungenutzten CodesErzeugung von nativem CodeRID-spezifische ausführbare Datei

Gewöhnliches .NET erzeugt zunächst IL und kompiliert zur Laufzeit nur das, was benötigt wird, per JIT. Native AOT verlagert einen großen Teil dieser späteren Phase vor auf den Zeitpunkt des Publish.

Wichtig dabei ist, dass zum Zeitpunkt des Publish „im Wesentlichen der gesamte zur Laufzeit benötigte Code bereits bekannt sein muss“. Genau hier verschiebt sich, was an Schreibweisen erlaubt ist.

  • Zur Laufzeit Typen suchen
  • Zur Laufzeit Code erzeugen
  • Zur Laufzeit Assemblys laden
  • Zur Laufzeit nach dem Prinzip „wird schon irgendwie gehen“ verzögert auflösen

Schreibweisen dieser Art vertragen sich mit Native AOT plötzlich schlecht.

4. Was Native AOT bringt

4.1. Der Start wird tendenziell leichter

Der offensichtlichste Effekt von Native AOT ist, wenig überraschend, der Start.

  • CLI-Tools
  • Kurzlebige Prozesse
  • Serverless-artiger Start
  • Start und Austausch von Containern
  • Überwachungstools und kleine residente Prozesse

In diesen Fällen sind die JIT-Kosten gut sichtbar, und weil Native AOT sie vorwegnimmt, wird der erste Moment leichter.

Auch der Speicherbedarf verbessert sich tendenziell, was von Vorteil ist, wenn Sie möglichst viele Instanzen auf derselben Hardware unterbringen möchten. Besonders in der Cloud, wo viele Kopien desselben Prozesses gleichzeitig laufen, summiert sich dieser Unterschied spürbar.

4.2. Keine vorinstallierte Runtime nötig

Eine mit Native AOT veröffentlichte Anwendung lässt sich leichter auf Umgebungen ohne installierte .NET-Runtime ausführen.

Das ist unauffällig, aber bedeutsam.

  • Sie wollen dem Verteilungsziel nicht sagen müssen: „Bitte installieren Sie zuerst die .NET 9 Runtime“
  • Sie wollen schlankere Container-Images
  • Sie wollen nur ein kleines Tool ablegen und es einfach laufen lassen
  • Sie wollen oder dürfen in der Ausführungsumgebung keinen JIT zulassen

In solchen Situationen wird es allein dadurch, dass „die Runtime separat bereitgestellt werden muss“ entfällt, deutlich ruhiger.

„Keine Runtime nötig“ bedeutet hier, dass am Verteilungsziel keine separate .NET-Installation nötig ist. Es bedeutet nicht, dass die runtime-äquivalenten Teile innerhalb der Anwendung vollständig verschwinden.

4.3. Passt zu eingeschränkten Ausführungsumgebungen

Da Native AOT keinen Laufzeit-JIT verwendet, lässt es sich auch in Umgebungen einsetzen, in denen JIT nicht erlaubt ist.

Das wirkt eher im Bereich Cloud, Container und Mobile als im Desktop-Umfeld. Aber auch im Kontext der Windows-Entwicklung ist es in dem Sinne durchaus erfreulich, dass sich unnötige Voraussetzungen am Verteilungsziel reduzieren lassen.

5. Wo Native AOT schwierig wird

5.1. Reflection und dynamische Codeerzeugung

Hier liegt der Kern der Einschränkungen von Native AOT.

  • Dynamisches Laden wie bei Assembly.LoadFile
  • Codeerzeugung zur Laufzeit wie bei System.Reflection.Emit
  • Reflection, die zur Laufzeit uneingeschränkt Typen durchläuft
  • Schreibweisen, die zur Laufzeit generics nach Belieben zusammensetzen

Bei diesen Mustern lässt sich der benötigte Code zum Zeitpunkt des Publish nur schwer festlegen, sodass sie zum Nährboden für AOT-Warnungen werden.

Natürlich ist es nicht so einfach, dass eine einzige Zeile Reflection sofort das Aus bedeutet. Aber die Tendenz stimmt ausnahmslos: je mehr das Design in Richtung „zur Laufzeit ansehen und entscheiden“ geht, desto schwieriger wird es.

Bei den Warnungsnamen begegnet Ihnen häufig die Familie RequiresDynamicCode. Sie bedeutet „dieser Aufruf könnte unter AOT brechen“, daher ist es sicherer, sie nicht leichtfertig zu unterdrücken.

Bei Native AOT hilft folgendes Bild: „Cleverness zur Laufzeit“ verringern und „Explizitheit zur Build-Zeit“ erhöhen.

5.2. Man muss von Trimming ausgehend denken

Native AOT ist eng mit Trimming verknüpft. Leicht zu übersehen ist dabei, dass nicht nur der eigene Code zählt, sondern auch die Schreibweise der verwendeten Bibliotheken.

Besonders vorsichtig sollten Sie bei Folgendem sein.

  • Reflection-basierte Serialisierer
  • DI-/Plugin-Konstruktionen, die Typen per Laufzeit-Scan sammeln
  • Mechanismen, die Typen anhand von Zeichenkettennamen suchen und instanziieren
  • Bibliotheken, die auf dynamische Proxys oder IL-Erzeugung setzen

Erscheint hier eine Warnung und Sie denken sich nur „Publish ist ja durchgelaufen, also passt es“, wird die Rechnung später ziemlich unangenehm. Bei Native AOT sollten Sie Warnungen grundsätzlich ernsthaft lesen.

Allein mit „lest sie ernsthaft“ kommt man aber nicht weiter, deshalb hier die Reihenfolge, in der Sie vorgehen sollten. Auch die offizielle Dokumentation empfiehlt, in dieser Reihenfolge vorzugehen.

Reihenfolge Maßnahme Einsatzfall
1 Reflection weglassen Activator.CreateInstance(Type) lässt sich durch ein generisches Argument ersetzen, oder auf einen Source Generator umstellen
2 DynamicallyAccessedMembers ergänzen Reflection ist nötig, aber der Zieltyp ist zur Kompilierzeit bekannt
3 RequiresUnreferencedCode ergänzen Der Typname wird z. B. zur Laufzeit aus einer Zeichenkette bestimmt und lässt sich grundsätzlich nicht statisch analysieren
4 Mit UnconditionalSuppressMessage unterdrücken Letztes Mittel, wenn alles Obige nicht möglich ist und die Sicherheit bestätigt wurde

Am häufigsten tritt als erster Schritt Fall 2 auf: „Der Typ ist eigentlich bekannt, nur die Warnung erscheint trotzdem“. Der folgende Code liefert zum Beispiel IL2070.

// IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
void PrintMethodNames(Type type)
{
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

Erklären Sie per Attribut, dass beim Aufruf von GetMethods() die öffentlichen Methoden erhalten bleiben sollen, verschwindet die Warnung.

using System.Diagnostics.CodeAnalysis;

void PrintMethodNames(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

// Übergibt der Aufrufer den Typ per typeof, ist die Anforderung automatisch erfüllt
PrintMethodNames(typeof(DateTime));

Die aufgerufene API und die benötigte Angabe entsprechen sich meist. Bei GetMethod / GetMethods ist es PublicMethods, bei GetProperty / GetProperties PublicProperties, bei Activator.CreateInstance PublicParameterlessConstructor oder PublicConstructors. DynamicallyAccessedMemberTypes.All ist bequem, lässt aber sämtliche Member des Zieltyps erhalten und bläht dadurch die Größe auf, und die erhaltenen Member ziehen wiederum andere Warnungen nach sich – deshalb gilt als Grundsatz, das Minimum anzugeben.

Verschwindet die Warnung auch nach Hinzufügen des Attributs nicht, prüfen Sie, ob von der Stelle, an der Reflection verwendet wird, bis zurück zum Aufrufer der gesamte Pfad damit versehen ist. Fehlt es an einer einzigen Stelle unterwegs, reißt die Anforderung dort ab.

5.3. Veröffentlichung pro Plattform

Native AOT wird mit fest vorgegebener RID (Runtime Identifier) veröffentlicht. Das heißt, eine für win-x64 erstellte Datei lässt sich nicht einfach so unter linux-x64 ausführen.

  • Windows x64
  • Windows Arm64
  • Linux x64
  • Linux Arm64
  • macOS Arm64

Für jedes Ziel wird ein eigenes Veröffentlichungsergebnis erzeugt.

Das fühlt sich deutlich stärker nach einer „nativen Anwendung“ an als das gewöhnliche framework-dependent .NET.

5.4. Bei Windows-Desktop / COM ist besondere Vorsicht geboten

Im Kontext von KomuraSoft ist dieser Punkt besonders wichtig.

Unter Windows besitzt Native AOT kein built-in COM. Zudem verträgt sich WPF schlecht mit Trimming, und WinForms hängt stark von built-in COM-Marshalling ab – zumindest derzeit sollten Sie beide daher als „ersten Native-AOT-Kandidaten“ mit großer Vorsicht betrachten.

Halten wir den Maßstab für „derzeit“ hier fest. Die Angaben in diesem Artikel beruhen auf der offiziellen Dokumentation der .NET-8/9/10-Generation (Stand Juli 2026). Zu WPF und WinForms steht in Microsofts „Known trimming incompatibilities“ ausdrücklich: WPF hängt stark von Reflection und Laufzeit-Codeprüfung ab und funktioniert nach dem Trimming praktisch nicht mehr, WinForms hängt stark von built-in COM-Marshalling ab, und bei beiden ist die Trimming-Unterstützung auf Seiten des .NET SDK deaktiviert. Es handelt sich also nicht bloß um „man sollte vorsichtig sein“, sondern derzeit blockiert das SDK selbst. Diese Angabe kann sich künftig ändern, prüfen Sie daher zum Zeitpunkt des Lesens dieselbe Seite noch einmal.

Kurz gesagt:

  • WPF / WinForms selbst unvermittelt auf Native AOT umzustellen
  • COM-Interop mit gewohntem Gefühl einfach mitzubringen

sind Fälle, in denen Warnungen beim Publish und Einschränkungen zur Laufzeit sprunghaft zunehmen und die Aufwandskosten schnell in die Höhe schießen.

Umgekehrt sind

  • Konsole
  • Worker
  • kleine Web-APIs
  • Komponenten, die sich auch bei nativer Anbindung auf eine C-Funktionsgrenze reduzieren lassen

als Einstieg naheliegender.

Wird COM benötigt, kann es sinnvoller sein, entweder beim JIT zu bleiben oder das Design von Grund auf um ComWrappers / source-generated COM herum neu zu entwerfen.

6. Minimaler Ablauf

Vorher noch dies: Für das Publish von Native AOT wird eine native Toolchain benötigt. Führen Sie dotnet publish mit PublishAot aus, ohne diese vorzubereiten, scheitert nicht die Kompilierung, sondern der abschließende native Link-Schritt. Das ist die erste Hürde, richten Sie die Toolchain also vorher ein.

Umgebung Benötigt wird
Windows Visual Studio 2022 oder neuer. Die Workload „Desktopentwicklung mit C++“ mit allen Standardkomponenten installieren
Ubuntu 18.04 oder neuer sudo apt-get install clang zlib1g-dev
Alpine 3.15 oder neuer sudo apk add clang build-base zlib-dev
Fedora 39 oder neuer / RHEL 8 oder neuer sudo dnf install clang zlib-ng-devel zlib-ng-compat-devel zlib-devel
macOS Xcodes Command Line Tools (ab .NET 8 unterstützt)

Kurz gesagt handelt es sich um die Compiler-Toolchain sowie die Entwicklungspakete der Bibliotheken, von denen die .NET-Runtime abhängt. Läuft die Erstellung in CI, lohnt es sich, die Installationsschritte aus den Native-AOT-Beispielen von dotnet/samples zu übernehmen – dort liegen Dockerfiles sowohl für Linux als auch für Windows vor.

Beachten Sie außerdem, dass eine unter Linux gebaute Binärdatei nur auf demselben oder einem neueren Linux läuft. Eine unter Ubuntu 20.04 erstellte Datei läuft ab 20.04 aufwärts, aber nicht unter 18.04. Die Wahl der Build-Umgebung bestimmt damit direkt den möglichen Verteilungsbereich.

6.1. csproj

Zunächst fügen Sie PublishAot in die Projektdatei ein.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <PublishAot>true</PublishAot>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

Für das Beispiel genügt net8.0. Die grundsätzliche Überlegung ist unter .NET 9 / 10 nahezu dieselbe.

Wichtig ist, es nicht nur vorübergehend an die Befehlszeile von dotnet publish anzuhängen, sondern es dauerhaft im Projekt zu belassen, sodass Sie die Analyse bei Build / Publish im Alltag im Blick behalten.

Das Setzen von <PublishAot>true</PublishAot> macht Ihre gewöhnliche lokale Ausführung nicht plötzlich zu Native AOT. Das alltägliche dotnet run und die normale Ausführung laufen weiterhin per JIT; die eigentliche Native-AOT-Kompilierung findet erst beim Publish statt.

6.2. Publish

Für Windows x64 sieht das zum Beispiel so aus.

dotnet publish -c Release -r win-x64

Für Linux x64 so.

dotnet publish -c Release -r linux-x64

Die Ausgabe ist RID-fixiert. Statt „eine einzige .NET-DLL, die überall läuft“ ist es nun „eine für dieses Betriebssystem / diese Architektur erstellte ausführbare Datei“.

Kommen Sie von der Web-API-Seite, ist der Einstieg über die Native-AOT-Vorlage der bequemste Weg.

dotnet new webapiaot -o MyFirstAotWebApi

Für einen Worker ist es diese hier.

dotnet new worker -o WorkerWithAot --aot

6.3. Umgang mit JSON

Ein unauffälliger, aber häufiger Stolperpunkt bei Native AOT ist JSON. Verwendet man System.Text.Json mit den gewohnten Reflexen, neigt es zu Reflection, daher ist es ruhiger, auf Source Generation zu setzen.

using System.Text.Json;
using System.Text.Json.Serialization;

[JsonSerializable(typeof(AppConfig))]
internal partial class AppJsonContext : JsonSerializerContext
{
}

public sealed class AppConfig
{
    public string? Name { get; init; }
    public int RetryCount { get; init; }
}

var config = new AppConfig
{
    Name = "sample",
    RetryCount = 3
};

string json = JsonSerializer.Serialize(config, AppJsonContext.Default.AppConfig);

In der Praxis trifft man am wenigsten daneben, wenn man es weniger als „Native-AOT-Kompatibilität“ versteht und mehr als „die Laufzeit nicht nach Typen suchen lassen“.

7. Geeignete Fälle

Native AOT passt angenehm in Fälle wie diese.

  • CLI-Tools / Werkzeuge, bei denen der Start im Mittelpunkt steht
  • Kleine, in Containern massenhaft verteilte APIs
  • Worker / Hintergrunddienste
  • Serverless- und kurzlebige Prozesse
  • Kleine .NET-Komponenten, die in native Anwendungen eingebunden werden
  • Situationen, in denen Sie keine vorinstallierte .NET-Runtime in der Ausführungsumgebung voraussetzen möchten

Gemeinsam ist ihnen, dass die Grenzen verhältnismäßig klar sind und sich dynamische Mechanismen leicht reduzieren lassen.

8. Ungeeignete Fälle

Umgekehrt gibt es klar erkennbare Fälle, in denen Native AOT von Anfang an nicht das Hauptfeld sein sollte.

  • Der Hauptteil einer bestehenden, großen WPF-/WinForms-Anwendung
  • Architekturen, die built-in COM-Interop voraussetzen
  • Anwendungen, bei denen das Laden von Plugins zur Laufzeit im Mittelpunkt steht
  • Konstruktionen mit starker Abhängigkeit von Frameworks, die Typen per Reflection ermitteln
  • Bibliotheken, die System.Reflection.Emit oder dynamische Proxys wie selbstverständlich nutzen
  • Designs mit C++/CLI in der Mitte

Hier sind gewöhnliches, JIT-basiertes .NET, ReadyToRun oder eine Überarbeitung der Design-Grenzen die sinnvollere Wahl.

9. Stolperfallen

Zum Schluss eine Zusammenfassung der Punkte, die man beim ersten Einsatz von Native AOT leicht übersieht.

  • Publish-Warnungen auf die leichte Schulter nehmen
    • Wie zuvor erwähnt, sollten Sie Native-AOT-Warnungen ernsthaft lesen.
  • Build läuft durch, aber Publish bricht
    • Beim Publish läuft die Analyse ernsthaft auch über die Abhängigkeiten, und manches wird erst hier sichtbar.
  • ReadyToRun und Native AOT mit derselben Haltung behandeln
    • Die Begriffe klingen ähnlich, aber die Stärke der Einschränkungen unterscheidet sich deutlich.
  • Direkt mit dem Hauptteil einer Desktop-Anwendung beginnen
    • Konsole / Worker / kleine API sind als Erstes ruhiger.
  • JSON oder Konfigurationsbindung wie gewohnt schreiben
    • Reflection-lastige Schreibweisen rächen sich später.
  • So verteilen, als sei es plattformunabhängig
    • Das Verteilungsergebnis von Native AOT ist RID-fixiert.
  • Glauben, „Native AOT = alles wird schneller“
    • Im Mittelpunkt stehen Start, Verteilung und Ausführungsumgebung. Verfehlt man das, weicht die Erwartung ab.

Bei Native AOT entscheidet letztlich nicht dotnet build, sondern dotnet publish darüber, ob etwas geht. Fangen Sie früh an, dies regelmäßig auszuführen, geraten Sie später seltener in Schwierigkeiten.

10. Zusammenfassung

Auf einen Satz gebracht ist Native AOT ein Mechanismus, der eine .NET-Anwendung von einem dynamischen Ausführungsmodell zu einem statisch festlegbaren Verteilungsmodell verschiebt.

Die wichtigen Punkte lassen sich auf diese fünf zusammenfassen.

  1. Native AOT kompiliert beim Publish vorab in nativen Code
  2. Es wirkt sich gut auf Start, Speicher und Verteilung aus
  3. Im Gegenzug ist es streng gegenüber Reflection, dynamischer Codeerzeugung, built-in COM und trimming-unfähigem Code
  4. Als erstes Ziel sind Konsole / Worker / kleine APIs ruhiger als der Hauptteil einer Desktop-Anwendung
  5. Wichtig ist, Warnungen nach und nach zu beseitigen und früh auf Basis von Publish zu prüfen

Native AOT ist kein Standardschalter, den man an jede .NET-Anwendung anlegt. Aber dort, wo der Start wichtig ist, die Verteilung leichter werden soll und weniger Voraussetzungen an die Ausführungsumgebung gestellt werden sollen, ist es eine ziemlich starke Waffe.

Umgekehrt ist in der dichten Welt von WPF / WinForms / COM oft noch gewöhnliches .NET die sinnvollere Wahl. Gelingt diese Unterscheidung, wird Native AOT nicht zu einem „schwierigen neuen Feature“, sondern zu einer Option mit klar erkennbarem Einsatzort.

11. Quellen

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Was ist Native AOT?
Native AOT ist ein Veröffentlichungsmodell, das eine .NET-Anwendung beim Publish vorab in nativen Code kompiliert und in dieser Form ausliefert. Da kein Laufzeit-JIT verwendet wird, verbessern sich Startzeit und Speicherbedarf tendenziell, und die Anwendung lässt sich leichter auf Umgebungen ohne installierte .NET-Runtime verteilen. Im Gegenzug verträgt es sich schlecht mit freizügiger Reflection, dynamischer Codeerzeugung, built-in COM und Bibliotheken ohne Trimming-Unterstützung. Es ist weniger ein Zaubertrick für mehr Geschwindigkeit als vielmehr ein Veröffentlichungsmodell, das zugunsten von Start, Verteilung und Ausführungsumgebung einen Teil der dynamischen Welt aufgibt und sich der statischen Welt annähert.
Was ist der Unterschied zwischen Native AOT und ReadyToRun?
ReadyToRun behält die IL bei und nimmt nur einen Teil der JIT-Arbeit vorweg – der Laufzeit-JIT kommt dabei weiterhin zum Einsatz. Die Kompatibilität bleibt breit, und dynamische Funktionen lassen sich größtenteils weiterhin bequem nutzen. Native AOT dagegen setzt den Laufzeit-JIT selbst nicht voraus, das Verteilungsergebnis besteht überwiegend aus einer nativen ausführbaren Datei, der Start lässt sich dadurch deutlich verbessern, dafür werden die Einschränkungen stärker. Auch wenn beide das Wort „AOT“ tragen, geht ReadyToRun in Richtung „dem JIT etwas Arbeit abnehmen“, während Native AOT in Richtung „den JIT gar nicht erst voraussetzen“ geht – die Grundstimmung ist ziemlich unterschiedlich.
Kann ich Native AOT bei WPF- oder WinForms-Anwendungen einsetzen?
Derzeit ist hier eher Vorsicht geboten. Native AOT unter Windows besitzt kein built-in COM, WPF verträgt sich schlecht mit Trimming, und WinForms hängt stark von built-in COM-Marshalling ab – beide eignen sich deshalb nicht als erste Kandidaten für Native AOT. Wird COM benötigt, kann es sinnvoller sein, entweder beim JIT zu bleiben oder das Design von Grund auf um ComWrappers / source-generated COM herum neu zu entwerfen. Als Einstieg sind Konsole, Worker und kleine Web-APIs naheliegender.
Für welche Art von Anwendung eignet sich Native AOT?
Es eignet sich für CLI-Tools, bei denen der Start im Mittelpunkt steht, für kleine, in Containern massenhaft verteilte APIs, für Worker und Hintergrunddienste, für Serverless- und kurzlebige Prozesse, für kleine .NET-Komponenten, die in native Anwendungen eingebunden werden, sowie für Situationen, in denen Sie keine vorinstallierte .NET-Runtime in der Ausführungsumgebung voraussetzen möchten. Gemeinsam ist diesen Fällen, dass die Grenzen verhältnismäßig klar sind und sich dynamische Mechanismen leicht reduzieren lassen. Umgekehrt eignet es sich nicht für Anwendungen, bei denen das Laden von Plugins zur Laufzeit im Mittelpunkt steht, oder für Konstruktionen mit starker Abhängigkeit von Frameworks, die Typen per Reflection ermitteln.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog