mcpField
D
dependency-injection

dependency-injection

github.com/codewithmukesh開発7913.0k 回インストール12 週間前に更新
リポジトリ

dependency-injection について

.NET 開発者向けの依存性注入ガイドで、登録、サービスのライフタイム、代表的なパターンを解説します。既存登録のライフタイムや重複、依存リスクを調べる MCP ツールにも触れています。

主な機能

  1. 01コンストラクター注入とインターフェース登録
  2. 02サービスのライフタイム選択
  3. 03キー付きサービスとファクトリーパターン
  4. 04Scrutor による規約ベース登録
  5. 05型付きオプションの設定

活用シーン

  1. 01インターフェースの実装を切り替える
  2. 02規約に沿ってサービスを登録する
  3. 03型付きオプションを設定して検証する
  4. 04ライフタイムの問題や重複登録を調べる

MCP 対応

README では `get_di_registrations` MCP ツールに言及しており、依存性注入登録のライフタイム、重複、キャプティブ依存のリスクを確認できます。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill dependency-injection

インストール後、Claude Code で/dependency-injectionを入力して読み込まれたか確認します。

詳細

依存性注入

基本原則

  1. コンストラクターインジェクションをデフォルトにする — 依存関係はコンストラクター経由で注入します(プライマリコンストラクターを使うと簡潔に記述できます)。サービスロケーターやプロパティインジェクションは使用しません。
  2. ライフタイムを慎重に設定する — シングルトンがスコープ付きサービスや一時サービスに依存してはいけません。これは DI で最もよく起こるバグです。
  3. インターフェースを登録し、インターフェースで解決する — 具象型ではなく、services.AddScoped<IOrderService, OrderService>() を登録します。
  4. ストラテジーパターンにはキー付きサービスを使う — .NET 8+ のキー付きサービスを使えば、実装を選択するための手動のファクトリーパターンを置き換えられます。

パターン

キー付きサービス(.NET 8+)

同じインターフェースの複数の実装を登録および解決するには、キー付きサービスを使用します。

// Registration
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");
builder.Services.AddKeyedScoped<INotificationService, PushNotificationService>("push");

// Resolution via attribute
public class OrderHandler([FromKeyedServices("email")] INotificationService notifier)
{
    public async Task Handle(CreateOrder.Command command, CancellationToken ct)
    {
        // ... create order
        await notifier.SendAsync(notification, ct);
    }
}

// Resolution via IServiceProvider
public class NotificationRouter(IServiceProvider provider)
{
    public INotificationService GetService(string channel)
    {
        return provider.GetRequiredKeyedService<INotificationService>(channel);
    }
}
デコレーターパターン
// Base service
public interface IOrderService
{
    Task<Result<Order>> CreateAsync(CreateOrderRequest request, CancellationToken ct);
}

public class OrderService(AppDbContext db, TimeProvider clock) : IOrderService
{
    public async Task<Result<Order>> CreateAsync(CreateOrderRequest request, CancellationToken ct)
    {
        var order = Order.Create(request, clock.GetUtcNow());
        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);
        return Result.Success(order);
    }
}

// Decorator — adds logging
public class LoggingOrderService(IOrderService inner, ILogger<LoggingOrderService> logger) : IOrderService
{
    public async Task<Result<Order>> CreateAsync(CreateOrderRequest request, CancellationToken ct)
    {
        logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
        var result = await inner.CreateAsync(request, ct);
        if (result.IsSuccess)
            logger.LogInformation("Order {OrderId} created", result.Value.Id);
        return result;
    }
}

// Registration with Scrutor
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.Decorate<IOrderService, LoggingOrderService>();
規約による登録(Scrutor)
// Auto-register all services matching a convention
builder.Services.Scan(scan => scan
    .FromAssemblyOf<Program>()
    .AddClasses(classes => classes.AssignableTo<ITransientService>())
    .AsImplementedInterfaces()
    .WithTransientLifetime()
    .AddClasses(classes => classes.AssignableTo<IScopedService>())
    .AsImplementedInterfaces()
    .WithScopedLifetime());
ファクトリーパターン

実行時のロジックで実装を選択する必要がある場合に使用します。

builder.Services.AddScoped<IPaymentProcessor>(sp =>
{
    var config = sp.GetRequiredService<IOptions<PaymentOptions>>().Value;
    return config.Provider switch
    {
        "stripe" => ActivatorUtilities.CreateInstance<StripeProcessor>(sp),
        "paypal" => ActivatorUtilities.CreateInstance<PayPalProcessor>(sp),
        _ => throw new InvalidOperationException($"Unknown payment provider: {config.Provider}")
    };
});
オプションの登録
// Bind configuration section to a strongly-typed options class
builder.Services.AddOptions<JwtOptions>()
    .BindConfiguration("Jwt")
    .ValidateDataAnnotations()
    .ValidateOnStart();

// Inject as IOptions<T>
public class TokenService(IOptions<JwtOptions> options)
{
    private readonly JwtOptions _jwt = options.Value;
}

アンチパターン

シングルトンにスコープ付きサービスを保持させない
// BAD — DbContext is scoped, captured by singleton = memory leak + stale data
builder.Services.AddSingleton<OrderCache>(); // depends on AppDbContext

// GOOD — use IServiceScopeFactory in singleton
public class OrderCache(IServiceScopeFactory scopeFactory)
{
    public async Task<Order?> GetAsync(Guid id)
    {
        await using var scope = scopeFactory.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        return await db.Orders.FindAsync(id);
    }
}
すべてをシングルトンとして登録しない
// BAD — making a service singleton when it holds mutable state
builder.Services.AddSingleton<OrderService>(); // has DbContext dependency

// GOOD — match the lifetime to the service's needs
builder.Services.AddScoped<OrderService>();

選択ガイド

シナリオ推奨事項
ステートレスなサービススコープ付き(デフォルト)または一時
構成/キャッシュシングルトン
DbContextスコープ付き(AddDbContext によって登録)
複数の実装キー付きサービス(ストラテジーパターン)
横断的な処理デコレーターパターン
規約ベースの登録Scrutor
実行時の実装選択ファクトリーデリゲート
既存の登録の監査get_di_registrations MCP ツール — ライフタイム、重複、キャプティブ依存関係のリスクを一度の呼び出しで確認
強く型付けされた構成AddOptions<T>().BindConfiguration()

コメント