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
- Zuerst das Fazit (in einem Satz)
- Die erste Übersichtstabelle
- 2.1. Begriffe rund um Native AOT
- 2.2. Der Unterschied zwischen JIT / ReadyToRun / Native AOT
- Das Gesamtbild von Native AOT (Diagramm)
- 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
- 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
- Minimaler Ablauf
- 6.1.
csproj - 6.2. Publish
- 6.3. Umgang mit JSON
- 6.1.
- Geeignete Fälle
- Ungeeignete Fälle
- Stolperfallen
- Zusammenfassung
- 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.
flowchart TD
S["Verteilungsform festlegen"] --> Q1{"Kann am Zielort eine .NET-Runtime installiert werden?"}
Q1 -- "Ja, geht" --> Q2{"Soll die Startzeit verkürzt werden?"}
Q2 -- "Nicht unbedingt" --> P1["framework-dependent<br/>gewöhnliche Veröffentlichung"]
Q2 -- "Ja, verkürzen" --> P2["ReadyToRun<br/>Startverbesserung bei erhaltener Kompatibilität"]
Q1 -- "Will/kann nicht installiert werden" --> Q3{"Bestehen Abhängigkeiten von Reflection, dynamischer Codeerzeugung oder built-in COM?"}
Q3 -- "Ja, abhängig" --> P3["self-contained<br/>bei Bedarf mit single-file bündeln"]
Q3 -- "Nein, unabhängig" --> Q4{"Ist das Ziel Konsole / Worker / kleine API?"}
Q4 -- "Ja" --> P4["Native AOT"]
Q4 -- "Nein" --> P5["zunächst self-contained<br/>dynamische 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.
flowchart LR
Src["C# / .NET-Quellcode"] --> IL["IL-Assemblys"]
IL -->|normale Ausführung| JIT["Laufzeit-JIT"]
JIT --> Run1["Anwendung läuft"]
IL -->|dotnet publish + PublishAot| Analyze["AOT-/Trim-Analyse"]
Analyze --> Trim["Entfernen ungenutzten Codes"]
Trim --> AOT["Erzeugung von nativem Code"]
AOT --> Run2["RID-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.Emitoder 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.
- Native AOT kompiliert beim Publish vorab in nativen Code
- Es wirkt sich gut auf Start, Speicher und Verteilung aus
- Im Gegenzug ist es streng gegenüber Reflection, dynamischer Codeerzeugung, built-in COM und trimming-unfähigem Code
- Als erstes Ziel sind Konsole / Worker / kleine APIs ruhiger als der Hauptteil einer Desktop-Anwendung
- 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
- Native AOT deployment overview - .NET
- Native AOT deployment overview - .NET (Japanisch)
- Introduction to AOT warnings - .NET
- Fixing trim warnings - .NET
- DynamicallyAccessedMembersAttribute Class - .NET
- Prepare .NET libraries for trimming - .NET
- Known trimming incompatibilities - .NET
- How to use source generation in System.Text.Json - .NET
- ASP.NET Core support for Native AOT
- ReadyToRun deployment overview - .NET
- Building native libraries - .NET
- ComWrappers source generation - .NET
- Verwandter Artikel: C# Native AOT DLL aus C/C++ aufrufen
- Verwandter Artikel: Native DLLs aus C# aufrufen: C++/CLI-Wrapper vs. P/Invoke
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Praktische Best Practices für Multithreading: .NET-Edition — Was Sie entscheiden sollten, bevor Sie weitere Threads hinzufügen
Eine praxisnahe Übersicht der Entwurfsregeln, die verhindern, dass Multithreading-Code in .NET/C# gelegentlich abstürzt oder hängen bleib...
Code-Design für Geschäftssysteme — Produkt- und Kundencodes festlegen, und Prüfziffern
Ein praxisnaher Leitfaden zur Festlegung des Codesystems eines Geschäftssystems, einschließlich Produkt- und Kundencodes. Behandelt eine ...
Was ist der .NET Generic Host? - Das Fundament für DI, Konfiguration und Logging
Wir ordnen die Rolle des Generic Host anhand seiner Beziehung zu DI, Konfiguration, Logging, IHostedService und BackgroundService ein und...
Die richtige Wahl unter .NETs drei Timern – PeriodicTimer/Timer/DispatcherTimer
Die Unterschiede zwischen PeriodicTimer, System.Threading.Timer und DispatcherTimer – und wie Sie sie für async-Verarbeitung, ThreadPool-...
Eine C# Native AOT DLL aus C/C++ aufrufen
Wie man eine C#-Klassenbibliothek mit Native AOT als native DLL veröffentlicht und Einstiegspunkte mit UnmanagedCallersOnly aus C/C++ auf...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
32-/64-Bit-Interoperabilität
32-/64-Bit-Kompatibilität, native Grenzen und Windows-Entscheidungen.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Technische Beratung und Design-Review
Klärung von Änderungsstrategie, Entwurf und Umgang mit bestehenden Assets.
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.