1. はじめに
- JavaのSpring BootでDIをアノテーションを使ってできるように、C#でも同じようにやりたい
 - ASP.NET CoreでService~DaoまでをDIできることを確認したい
 
2. 開発環境
- C#
 - .NET 6 (ASP.Net Core)
 - Visual Studio 2022
 - Windows 11
 - Microsoft.Extensions.DependencyInjection(NuGet)
 
3. DIの実装
こちらの記事に掲載しているソースを使用させてもらいました。
3.1. 属性の作成
ComponentAttribute.cs
using System;
namespace NX.DependencyInjection
{
    /// <summary>
    /// <see cref="Microsoft.Extensions.DependencyInjection.IServiceCollection" />にコンポーネントを自動登録するための属性
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public class ComponentAttribute : Attribute
    {
        /// <summary>
        /// コンポーネントのライフサイクル。未設定の場合は<see cref="ComponentScope.Scoped"/>。
        /// </summary>
        public ComponentScope Scope { get; set; } = ComponentScope.Scoped;
        /// <summary>
        /// コンポーネントの登録対象の型。未設定の場合はコンポーネント自身の型。
        /// </summary>
        public Type? TargetType { get; set; }
        public ComponentAttribute()
        {
        }
        public ComponentAttribute(ComponentScope scope, Type? targetType = null)
        {
            Scope = scope;
            TargetType = targetType;
        }
    }
}
3.2. リフレクションの実装
ComponentLoader.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace NX.DependencyInjection
{
    public static class ComponentLoader
    {
        public static IEnumerable<ComponentInfo> Load(string assemblyName)
        {
            try
            {
                return Load(Assembly.Load(assemblyName));
            }
            catch (Exception e)
            {
                throw new AssemblyLoadException($"Failed to load assembly \"{assemblyName}\"", e);
            }
        }
        public static IEnumerable<ComponentInfo> Load(Assembly assembly)
        {
            // アセンブリ内のクラスから指定の属性を持つもののみを抽出
            return assembly.GetTypes()
                .SelectMany(type =>
                    type.GetCustomAttributes<ComponentAttribute>().Select(attr => (Type: type, Attr: attr)))
                .Select(x => new ComponentInfo(x.Attr.Scope, x.Attr.TargetType ?? x.Type, x.Type));
        }
        // ComponentAttributeの情報を保持しておくための箱
        public class ComponentInfo
        {
            public ComponentScope Scope { get; }
            public Type TargetType { get; }
            public Type ImplementType { get; }
            public ComponentInfo(ComponentScope scope, Type targetType, Type implementType)
            {
                Scope = scope;
                TargetType = targetType;
                ImplementType = implementType;
                if (!TargetType.IsAssignableFrom(ImplementType))
                {
                    throw new ArgumentException($"Type \"{TargetType.FullName}\" is not assignable from \"{ImplementType.FullName}\"");
                }
            }
        }
    }
}
3.3. 依存性注入機能への登録
ServiceCollectionExtensions.cs
using System;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace NX.DependencyInjection
{
    public static class ServiceCollectionExtensions
    {
        /// <summary>
        /// 指定のアセンブリからコンポーネントを検索して自動登録します。
        /// </summary>
        /// <param name="services">コンポーネントを登録するServiceCollection</param>
        /// <param name="assemblyNames">コンポーネントを検索するアセンブリの名前一覧</param>
        public static IServiceCollection RegisterComponents(this IServiceCollection services, params string[] assemblyNames)
        {
            foreach (var ci in assemblyNames.SelectMany(ComponentLoader.Load))
            {
                var lifetime = ci.Scope switch
                {
                    ComponentScope.Singleton => ServiceLifetime.Singleton,
                    ComponentScope.Scoped => ServiceLifetime.Scoped,
                    ComponentScope.Transient => ServiceLifetime.Transient,
                    _ => throw new InvalidOperationException("Unknown ComponentScope value")
                };
                services.Add(new ServiceDescriptor(ci.TargetType, ci.ImplementType, lifetime));
            }
            return services;
        }
    }
}
3.4. 依存性注入機能への登録
- 
// Add services to the container.の下に、DIを自動登録するように記述を追加 
Program.cs
using NX.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// ---
// DIするサービスを自動登録する
AppDomain.CurrentDomain.GetAssemblies();
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
    builder.Services.RegisterComponents(asm.FullName!);
}
// ---
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
4. DIの使用
4.1. フォルダ構成
DiSample                           # ASP.Net Core 
  /Controllers         
    WeatherForecastController.cs   # コントローラ 
  /Services         
    IDiService.cs                  # サービスI/F 
  /Service /Impl         
    DiServiceImpl.cs               # サービス実装 
  /Logics         
    DiLogic.cs                     # ロジック 
  /Daos         
    DiDaoImpl.cs                   # Dao I/F 
  /Daos /Impl         
    DiDaoImpl.cs                   # Dao 実装 
            
  Program.cs                       # プログラムファイル 
4.2. Controller
・コンストラクタにDIするクラスをパラメータで受け取り、Private変数に格納する
WeatherForecastController.cs
using DiSample.Services;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace DiSample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };
        private readonly ILogger<WeatherForecastController> _logger;
        private readonly IDiService _diService;
        public WeatherForecastController(ILogger<WeatherForecastController> logger, IDiService diService)
        {
            _logger = logger;
            _diService = diService;
        }
        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {
            // DIしたサービスを実行
            Debug.Print(_diService.Get());
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}
4.3. Services/ServiceImpl
・サービス実装クラスに属性を追加する
 [Service(Scope = ComponentScope.Singleton, TargetType = typeof(IDiService))]
・サービス実装クラスのコンストラクタにDIするクラスをパラメータで受け取り、Private変数に格納する
IDiService.cs
namespace DiSample.Services
{
    public interface IDiService
    {
        public string Get();
    }
}
DiServiceImpl.cs
using DiSample.Logics;
using NX.DependencyInjection;
using System.Diagnostics;
namespace DiSample.Services.Impl
{
    [Service(Scope = ComponentScope.Singleton, TargetType = typeof(IDiService))]
    public class DiServiceImpl : IDiService
    {
        private readonly DiLogic _diLogic;
        // コンストラクタ
        public DiServiceImpl(DiLogic diLogic)
        {
            _diLogic = diLogic;
        }
        public string Get()
        {
            // 実行確認
            Debug.Print("Run DiServiceImpl");
            return _diLogic.Get();
        }
    }
}
4.4. Logic
・ロジッククラスに属性を追加する
 [Logic(Scope = ComponentScope.Singleton, TargetType = typeof(DiLogic))]
・ロジッククラスのコンストラクタにDIするクラスをパラメータで受け取り、Private変数に格納する
DiLogic.cs
using DiSample.Daos;
using DiSample.Services;
using NX.DependencyInjection;
using System.Diagnostics;
namespace DiSample.Logics
{
    [Logic(Scope = ComponentScope.Singleton, TargetType = typeof(DiLogic))]
    public class DiLogic
    {
        private readonly IDiDao _diDao;
        // コンストラクタ
        public DiLogic(IDiDao diDao)
        {
            _diDao = diDao;
        }
        public string Get()
        {
            // 実行確認
            Debug.Print("Run DiLogic");
            return _diDao.Get();
        }
    }
}
4.5. Dao/DaoImpl
・Dao実装クラスに属性を追加する
 [Dao(Scope = ComponentScope.Singleton, TargetType = typeof(IDiDao))]
IDiDao.cs
using DiSample.Services;
namespace DiSample.Daos
{
    public interface IDiDao
    {
        public string Get();
    }
}
DiDaoImpl.cs
using NX.DependencyInjection;
using System.Diagnostics;
namespace DiSample.Daos.Impl
{
    [Dao(Scope = ComponentScope.Singleton, TargetType = typeof(IDiDao))]
    public class DiDaoImpl : IDiDao
    {
        public string Get()
        {
            // 実行確認
            Debug.Print("Run DiDaoImpl");
            // ダミー実装
            return "DaoImpl:Get()";
        }
    }
}
[Logic(Scope = ComponentScope.Singleton, TargetType = typeof(IDiLogic))]
[Dao(Scope = ComponentScope.Singleton, TargetType = typeof(IDiDao))]
- ScopeはMicrosoft.Extensions.DependencyInjectionの設定と同じ
- ComponentScope.Singleton: シングルトンのスコープつまり、インスタンスは1回だけ生成される
 - ComponentScope.Scoped: 1回のリクエストに対して、インスタンスが1回生成される
 - ComponentScope.Transient: 毎回新しいインスタンスが生成される
 
 - TargetTypeはサービスのI/Fを設定する
 
5. 実行結果
6. ソースコード
7. 参考文献

