InjectAttribute を付与したクラスを自動的に IServiceCollection へ登録する C# Source Generator です。
- Incremental Generator (
IIncrementalGenerator) 採用 — 差分ビルドに対応し、高速にコード生成 - クラス単位で DI ライフタイムを指定 (
Transient/Scoped/Singleton) - サービス型(インターフェイス / 抽象クラス)を明示指定可能。省略時は実装型 = サービス型として登録
- 同一クラスへの複数属性 (
AllowMultiple = true) をサポートし、1 つの実装を複数のサービス型で登録可能 - 属性ライブラリは netstandard2.0 対応 — 幅広い .NET バージョンで利用可能
AutoDiAttributes/ … 属性・列挙型を定義するライブラリ (netstandard2.0)
AutoDiAttributes.Generator/ … Incremental Source Generator (netstandard2.0)
AutoDiAttributes.Generator.Tests/ … テスト (xUnit + Shouldly, net10.0)
DebugProject/ … デモ・動作確認用プロジェクト
AutoDiAttributes と AutoDiAttributes.Generator をソリューションに含めるか、NuGet パッケージとして参照します。
Note
AutoDiAttributes.Generator は Source Generator として参照する必要があります。
<ProjectReference Include="..\AutoDiAttributes.Generator\AutoDiAttributes.Generator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />using AutoDiAttributes;
// インターフェイスをサービス型として明示指定
[Inject(InjectServiceLifetime.Scoped, typeof(IMyService))]
public class MyService : IMyService
{
// 実装
}
// サービス型を省略 → 実装型自身がサービスとして登録される
[Inject(InjectServiceLifetime.Singleton)]
public class ClockProvider
{
// 実装
}
// 複数の属性を付与して、インターフェイスと実装型の両方を登録
[Inject(InjectServiceLifetime.Singleton, typeof(INotifier))]
[Inject(InjectServiceLifetime.Transient)]
public class NotifierService : INotifier
{
// INotifier → Singleton、NotifierService → Transient として登録される
}ビルドすると、アセンブリ名から導出された名前空間に DIRegistration クラスが自動生成されます。
アプリケーション起動時に拡張メソッド AddGeneratedServices() を呼び出してください。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGeneratedServices(); // 生成された拡張メソッドnamespace AutoDiAttributes;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class InjectAttribute : Attribute
{
public InjectAttribute(InjectServiceLifetime lifetime);
public InjectAttribute(InjectServiceLifetime lifetime, Type serviceType);
public InjectServiceLifetime Lifetime { get; }
public Type? ServiceType { get; }
}| パラメータ | 説明 |
|---|---|
lifetime |
DI ライフタイム。Transient (0) / Scoped (1) / Singleton (2) |
serviceType |
登録時のサービス型(インターフェイス / 抽象型)。省略時は実装クラス自身 |
namespace AutoDiAttributes;
public enum InjectServiceLifetime
{
Transient = 0,
Scoped = 1,
Singleton = 2
}Important
Microsoft.Extensions.DependencyInjection.ServiceLifetime ではなく、AutoDiAttributes 独自の列挙型を使用します。
これにより、属性ライブラリを netstandard2.0 のまま維持しつつ、DI フレームワークへの依存を排除しています。
アセンブリ名が MyApp.Web の場合、以下のようなコードが生成されます。
// <auto-generated />
namespace MyApp.Web;
public static class DIRegistration
{
public static void AddGeneratedServices(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)
{
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddScoped<global::MyApp.IMyService, global::MyApp.MyService>(services);
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::MyApp.ClockProvider, global::MyApp.ClockProvider>(services);
}
}生成コードの名前空間は、アセンブリ名から自動導出されます。 C# の識別子として不正な文字は以下のルールで変換されます:
| アセンブリ名 | 生成される名前空間 |
|---|---|
MyApp |
MyApp |
My.App |
My.App |
123App |
_23App |
My-App |
My_App |
My..App |
My._App |
| (空 / null) | GeneratedDI |
以下のいずれの書式でも正しく認識されます。
[Inject(...)]
[InjectAttribute(...)]
[AutoDiAttributes.Inject(...)]
[AutoDiAttributes.InjectAttribute(...)]
[global::AutoDiAttributes.Inject(...)]
[global::AutoDiAttributes.InjectAttribute(...)]dotnet testテストフレームワークには xUnit を、アサーションには Shouldly を使用しています。
| 症状 | 原因 | 対処 |
|---|---|---|
| 生成クラスが見えない | ビルド前の IntelliSense キャッシュ | 一度リビルドし、obj 配下の生成物を確認 |
AddGeneratedServices() が見つからない |
名前空間の import 不足 | アセンブリ名に対応する名前空間を using に追加 |
| 期待する登録が無い | 属性の名前空間が異なる / typo | using AutoDiAttributes; を追加し、属性名を確認 |
| ライフタイムが期待と違う | 引数順序違い | 第 1 引数 = InjectServiceLifetime、第 2 引数 = Type の順で指定 |
MIT