mcpField
D
dotnet-aot-architecture

dotnet-aot-architecture

github.com/wshaddix開発8669 回インストール35 週間前に更新
リポジトリ

dotnet-aot-architecture について

dotnet-aot-architecture は、.NET 8+ の AOT を前提とした設計パターンを提供するスキルです。コンソール、ASP.NET Core Minimal API、ワーカーサービスの開発者向けに、実行時リフレクションをソース生成、明示的 DI、AOT 対応シリアライズ、ファクトリーパターンで置き換える方法を扱います。

主な機能

  1. 01実行時リフレクションをソース生成に置換
  2. 02明示的な依存性注入登録
  3. 03AOT 対応シリアライズ方式の選択
  4. 04Activator.CreateInstance をファクトリーに置換
  5. 05ライブラリの AOT 互換性評価

活用シーン

  1. 01AOT 対応の .NET 8+ アプリ設計
  2. 02リフレクション依存の DI を明示登録に変更
  3. 03ソース生成シリアライズの設定
  4. 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 パッケージを採用する前に、次の点を確認してください。

  1. パッケージのソースに IsAotCompatible があるか確認する -- これを設定しているパッケージは、AOT アナライザーによる検証済みです。
  2. [RequiresDynamicCode] / [RequiresUnreferencedCode] の注釈を確認する -- これらは AOT 非互換の API を示しています。
  3. 利用箇所に対して AOT アナライザーを実行する -- dotnet build /p:EnableAotAnalyzer=true
  4. パッケージの GitHub の issue で AOT/トリミングに関する報告を確認する -- 「Native AOT」、「trimming」、「IL2026」、「IL3050」で検索してください。
  5. ソース生成を利用した代替手段を探す -- リフレクションベースのライブラリの多くに、現在はソース生成を利用した関連ライブラリがあります。
一般的なライブラリの状況
ライブラリ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

エージェントが注意すべき点

  1. AOT プロジェクトで Activator.CreateInstance を使用しないでください。 利用できないランタイムリフレクションが必要です。代わりに、ファクトリーパターン、DI のキー付きサービス、または switch 式を使用してください。
  2. DI 登録にアセンブリスキャン(Scan、RegisterAssemblyTypes、FromAssemblyOf)を使用しないでください。 これらはランタイムに型を検出するため、リフレクションを使用します。サービスは明示的に登録してください。
  3. AOT では、[JsonSerializable] コンテキストなしで System.Text.Json を使用しないでください。 ソース生成されたコンテキストがない場合、STJ はリフレクションにフォールバックし、ランタイムに失敗します。
  4. テストせずにライブラリが AOT 互換だと決めつけないでください。 dotnet build /p:EnableAotAnalyzer=true を実行し、実際の利用箇所で IL3050/IL2026 の警告が出ないか確認してください。
  5. ランタイムの検出に Type.GetType() や Assembly.GetTypes() を使用しないでください。 これらはトリミングされる可能性のあるメタデータに依存します。コンパイル時に既知の型を使用してください。

参考資料

コメント