dotnet-aot-architecture について
dotnet-aot-architecture は、.NET 8+ の AOT を前提とした設計パターンを提供するスキルです。コンソール、ASP.NET Core Minimal API、ワーカーサービスの開発者向けに、実行時リフレクションをソース生成、明示的 DI、AOT 対応シリアライズ、ファクトリーパターンで置き換える方法を扱います。
主な機能
- 01実行時リフレクションをソース生成に置換
- 02明示的な依存性注入登録
- 03AOT 対応シリアライズ方式の選択
- 04Activator.CreateInstance をファクトリーに置換
- 05ライブラリの AOT 互換性評価
活用シーン
- 01AOT 対応の .NET 8+ アプリ設計
- 02リフレクション依存の DI を明示登録に変更
- 03ソース生成シリアライズの設定
- 04NuGet パッケージの AOT 互換性確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-aot-architectureインストール後、Claude Code で/dotnet-aot-architectureを入力して読み込まれたか確認します。
詳細
dotnet-aot-architecture
.NET 8+ 向けの AOT を優先したアプリケーション設計パターンです。リフレクションよりソースジェネレーターを優先し、アセンブリスキャンより明示的な DI 登録を選び、AOT セーフなシリアル化方式を採用し、ライブラリの互換性を評価し、Activator.CreateInstance をファクトリーパターンで置き換えます。
バージョンの前提: .NET 8.0+ をベースラインとします。パターンは、AOT に対応したすべてのプロジェクトタイプ(コンソール、ASP.NET Core Minimal API、ワーカーサービス)に適用できます。
対象外: Native AOT の発行パイプラインと MSBuild の構成については、[skill:dotnet-native-aot] を参照してください。トリムセーフなライブラリの作成とアノテーションについては、[skill:dotnet-trimming] を参照してください。WASM AOT コンパイルについては、[skill:dotnet-aot-wasm] を参照してください。MAUI 固有の AOT については、[skill:dotnet-maui-aot] を参照してください。ソースジェネレーターの作成(Roslyn API)については、[skill:dotnet-csharp-source-generators] を参照してください。DI コンテナーの内部については、[skill:dotnet-csharp-dependency-injection] を参照してください。シリアル化の詳細については、[skill:dotnet-serialization] を参照してください。
相互参照:AOT の発行パイプラインについては [skill:dotnet-native-aot]、トリムアノテーションとライブラリの作成については [skill:dotnet-trimming]、シリアル化パターンについては [skill:dotnet-serialization]、ソースジェネレーターの仕組みについては [skill:dotnet-csharp-source-generators]、DI の基本については [skill:dotnet-csharp-dependency-injection]、runtime-deps のデプロイについては [skill:dotnet-containers]、一般的な P/Invoke パターンとマーシャリングについては [skill:dotnet-native-interop] を参照してください。
リフレクションよりソースジェネレーターを優先
AOT を実現する主な方法は、実行時のリフレクションをコンパイル時のソース生成に置き換えることです。ソースジェネレーターはビルド時にコードを生成するため、AOT コンパイラーが解析して取り込めます。
主なソースジェネレーターによる置き換え
| リフレクションのパターン | ソースジェネレーター/AOT セーフな代替手段 | ライブラリ |
|---|---|---|
JsonSerializer.Deserialize<T>() | [JsonSerializable] コンテキスト | System.Text.Json(組み込み) |
Activator.CreateInstance<T>() | 明示的な new を使うファクトリーパターン | 手動 |
マッピングに使う Type.GetProperties() | [Mapper] 属性 | Mapperly |
Regex パターンのコンパイル | [GeneratedRegex] 属性 | 組み込み(.NET 7+) |
文字列補間を使った ILogger.Log(...) | [LoggerMessage] 属性 | Microsoft.Extensions.Logging |
| DI のアセンブリスキャン | 明示的な services.Add*() | 手動 |
[DllImport] P/Invoke | [LibraryImport] | 組み込み(.NET 7+) |
AutoMapper の CreateMap<>() | [Mapper] ソース生成 | Mapperly |
例:ソース生成への移行
// BEFORE: Reflection-based (breaks under AOT)
var logger = loggerFactory.CreateLogger<OrderService>();
logger.LogInformation("Order {OrderId} created for {Customer}", order.Id, order.CustomerId);
// AFTER: Source-generated (AOT-safe, zero-alloc)
public partial class OrderService
{
[LoggerMessage(Level = LogLevel.Information,
Message = "Order {OrderId} created for {Customer}")]
private static partial void LogOrderCreated(
ILogger logger, int orderId, string customer);
}
// Usage:
LogOrderCreated(_logger, order.Id, order.CustomerId);
ソースジェネレーターの仕組みと作成パターンについては、[skill:dotnet-csharp-source-generators] を参照してください。
AOT セーフな DI パターン
AOT での依存性注入では、サービスを明示的に登録する必要があります。アセンブリスキャン(AddServicesFromAssembly)とオープンジェネリックの解決では、AOT では対応できないリフレクションが必要になる場合があります。
明示的な登録(推奨)
var builder = WebApplication.CreateSlimBuilder(args);
// Explicit registrations -- AOT-safe
builder.Services.AddSingleton<IOrderRepository, PostgresOrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
builder.Services.AddSingleton(TimeProvider.System);
アセンブリスキャンを避ける
// BAD: Assembly scanning uses reflection -- breaks under AOT
builder.Services.Scan(scan => scan
.FromAssemblyOf<OrderService>()
.AddClasses(classes => classes.AssignableTo<IService>())
.AsImplementedInterfaces()
.WithScopedLifetime());
// GOOD: Explicit registrations grouped by concern
builder.Services.AddOrderServices();
builder.Services.AddInventoryServices();
// Extension method groups related registrations
public static class OrderServiceExtensions
{
public static IServiceCollection AddOrderServices(
this IServiceCollection services)
{
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IOrderRepository, PostgresOrderRepository>();
services.AddScoped<IOrderValidator, OrderValidator>();
return services;
}
}
キー付きサービス(.NET 8+)
// AOT-safe keyed service registration
builder.Services.AddKeyedSingleton<INotificationSender, EmailSender>("email");
builder.Services.AddKeyedSingleton<INotificationSender, SmsSender>("sms");
// Resolve by key
app.MapPost("/notify", ([FromKeyedServices("email")] INotificationSender sender) =>
sender.SendAsync("Hello"));
DI パターンの詳細については、[skill:dotnet-csharp-dependency-injection] を参照してください。
AOT 向けのシリアル化方式
選択マトリックス
| シリアライザー | AOT セーフ | 必要な設定 | 最適な用途 |
|---|---|---|---|
| System.Text.Json + ソース生成 | はい | [JsonSerializable] コンテキスト | API、構成、JSON 連携 |
| Protobuf(Google.Protobuf) | はい | .proto スキーマファイル | gRPC、サービス間通信 |
| MessagePack + ソース生成 | はい | [MessagePackObject] + ソース生成リゾルバー | キャッシュ、リアルタイム処理 |
| Newtonsoft.Json | いいえ | 該当なし | AOT では使用しないでください |
| ソース生成なしの STJ | いいえ | 該当なし | リフレクションにフォールバックします |
STJ ソース生成の設定
// Define serializable types
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List<Product>))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal partial class AppJsonContext : JsonSerializerContext { }
// Register in ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonContext.Default);
});
シリアル化パターンの詳細については、[skill:dotnet-serialization] を参照してください。
Activator.CreateInstance を置き換えるファクトリーパターン
Activator.CreateInstance は実行時のリフレクションを使ってインスタンスを作成するため、AOT と互換性がありません。明示的な構築を使うファクトリーパターンに置き換えてください。
シンプルファクトリー
// BAD: Reflection-based creation -- breaks under AOT
public T CreateHandler<T>() where T : class
=> (T)Activator.CreateInstance(typeof(T))!;
// GOOD: Factory with explicit registration
public class HandlerFactory
{
private readonly Dictionary<Type, Func<IHandler>> _factories = new();
public void Register<T>(Func<T> factory) where T : IHandler
=> _factories[typeof(T)] = () => factory();
public IHandler Create<T>() where T : IHandler
=> _factories[typeof(T)]();
}
// Registration
var factory = new HandlerFactory();
factory.Register<OrderHandler>(() => new OrderHandler(repository, logger));
factory.Register<PaymentHandler>(() => new PaymentHandler(gateway));
DI によるストラテジーパターン
// BAD: Dynamic type resolution
public IPaymentProcessor GetProcessor(string type)
{
var processorType = Type.GetType($"MyApp.Payments.{type}Processor");
return (IPaymentProcessor)Activator.CreateInstance(processorType!)!;
}
// GOOD: Keyed services (.NET 8+)
builder.Services.AddKeyedScoped<IPaymentProcessor, CreditCardProcessor>("CreditCard");
builder.Services.AddKeyedScoped<IPaymentProcessor, BankTransferProcessor>("BankTransfer");
builder.Services.AddKeyedScoped<IPaymentProcessor, WalletProcessor>("Wallet");
// Resolve at runtime without reflection
app.MapPost("/pay", (
[FromQuery] string type,
IServiceProvider sp) =>
{
var processor = sp.GetRequiredKeyedService<IPaymentProcessor>(type);
return processor.ProcessAsync();
});
Enum ベースのファクトリー
// For a fixed set of types, use a switch expression
public static IExporter CreateExporter(ExportFormat format) => format switch
{
ExportFormat.Csv => new CsvExporter(),
ExportFormat.Json => new JsonExporter(),
ExportFormat.Pdf => new PdfExporter(),
_ => throw new ArgumentOutOfRangeException(nameof(format))
};
ライブラリの互換性評価
評価チェックリスト
AOT プロジェクトで NuGet パッケージを採用する前に、次の点を確認してください。
- パッケージのソースに
IsAotCompatibleがあるか確認する -- これを設定しているパッケージは、AOT アナライザーによる検証済みです。 [RequiresDynamicCode]/[RequiresUnreferencedCode]の注釈を確認する -- これらは AOT 非互換の API を示しています。- 利用箇所に対して AOT アナライザーを実行する --
dotnet build /p:EnableAotAnalyzer=true - パッケージの GitHub の issue で AOT/トリミングに関する報告を確認する -- 「Native AOT」、「trimming」、「IL2026」、「IL3050」で検索してください。
- ソース生成を利用した代替手段を探す -- リフレクションベースのライブラリの多くに、現在はソース生成を利用した関連ライブラリがあります。
一般的なライブラリの状況
| ライブラリ | AOT の状況 | AOT セーフな代替手段 |
|---|---|---|
| AutoMapper | 非互換 | Mapperly |
| MediatR | 部分対応(明示的な登録) | メソッドの直接呼び出しまたはファクトリー |
| FluentValidation | 部分対応 | 手動での検証またはソース生成 |
| Dapper | 互換(.NET 8+ AOT サポート) | -- |
| Entity Framework Core | 部分対応(プリコンパイル済みクエリ) | AOT を多用する処理には Dapper |
| Refit | 互換(ソース生成を利用した 7+) | -- |
| Polly | 互換(v8+) | -- |
| Serilog | 部分対応 | [LoggerMessage] ソース生成 |
| Hangfire | 非互換 | カスタム IHostedService |
互換性のテスト
# Build with all analyzers enabled
dotnet build /p:EnableAotAnalyzer=true /p:EnableTrimAnalyzer=true /p:TrimmerSingleWarn=false
# Warnings indicate AOT-incompatible usage
# IL3050 = RequiresDynamicCode (definitely breaks)
# IL2026 = RequiresUnreferencedCode (may break)
AOT アプリケーションのアーキテクチャテンプレート
src/
MyApp/
Program.cs # CreateSlimBuilder, explicit DI
MyApp.csproj # PublishAot=true, EnableAotAnalyzer=true
JsonContext.cs # [JsonSerializable] for all API types
Endpoints/
OrderEndpoints.cs # Minimal API route groups
ProductEndpoints.cs
Services/
OrderService.cs # Business logic (no reflection)
IOrderService.cs
Repositories/
OrderRepository.cs # Data access (Dapper or EF precompiled)
Extensions/
ServiceCollectionExtensions.cs # Grouped DI registrations
エージェントが注意すべき点
- AOT プロジェクトで
Activator.CreateInstanceを使用しないでください。 利用できないランタイムリフレクションが必要です。代わりに、ファクトリーパターン、DI のキー付きサービス、または switch 式を使用してください。 - DI 登録にアセンブリスキャン(
Scan、RegisterAssemblyTypes、FromAssemblyOf)を使用しないでください。 これらはランタイムに型を検出するため、リフレクションを使用します。サービスは明示的に登録してください。 - AOT では、
[JsonSerializable]コンテキストなしでSystem.Text.Jsonを使用しないでください。 ソース生成されたコンテキストがない場合、STJ はリフレクションにフォールバックし、ランタイムに失敗します。 - テストせずにライブラリが AOT 互換だと決めつけないでください。
dotnet build /p:EnableAotAnalyzer=trueを実行し、実際の利用箇所で IL3050/IL2026 の警告が出ないか確認してください。 - ランタイムの検出に
Type.GetType()やAssembly.GetTypes()を使用しないでください。 これらはトリミングされる可能性のあるメタデータに依存します。コンパイル時に既知の型を使用してください。