サイト管理者: Winform の依存性注入に関する記事は多くありません。この技術は比較的安定しており、必要なものはすべて揃っています。今回は他のアカウントで書かれたWinform依存性注入の記事を共有し、関連開発に携わる方々に新たな気づきがあればと思います。
1. はじめに
依存性注入とは何か?依存性注入は具体的なコーディング手法であり、私にとって最も顕著なのはコードの結合度を解消することです。
2. 目的
ASP.NET Core ではコンテナ自体がすでに作成されており、コンテナにサービスを追加するだけで済みます。しかし、Winform ではデフォルトで new による操作が行われています(すでに .NET6 にアップグレードしています)。最近、オープンソースプロジェクトに自作の機能を追加し、ついでに元の NetF を NetCore にアップグレードしたところ、依存性注入方式を試してみようと思いました。
C/S コードの経験が浅いため、間違いがあればご指摘ください。
3. 操作
本記事のサンプル環境: VS2022、.NET6、Windows フォームアプリ
4. 準備
サンプルには以下のコードが含まれています。
- フォーム: Form1、Form2
- Service: IUserservice、Userservice、IOrderService、OrderService
public interface IUserservice
{
string GetName();
}
public class UserService : IUserservice
{
public string GetName()
{
return "IUserservice";
}
}
public interface IOrderService
{
string GetName();
}
public class OrderService : IOrderService
{
public string GetName()
{
return "IOrderService";
}
}
5. シナリオ
Form1 でコンストラクタインジェクションにより IUserservice を注入し、Load イベント内で IUserservice の GetName メソッドを呼び出します。ページのボタンをクリックすると Form2 を表示し、Form2 では依存性注入により IOrderService を注入して Load イベント内で IOrderService の GetName メソッドを呼び出します。複数回操作してもエラーが発生しなければ成功です。
6. 開始
コンポーネントの参照追加
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
ServiceProviderHelper 操作クラスを追加
public static class ServiceProviderHelper
{
/// <summary>
/// グローバルサービスプロバイダー
/// </summary>
public static IServiceProvider ServiceProvider { get; private set; } = null!;
/// <summary>
/// ServiceProvider オブジェクトの初期構築
/// </summary>
/// <param name="serviceProvider"></param>
/// <exception cref="ArgumentNullException"></exception>
public static void InitServiceProvider(ServiceProvider serviceProvider)
{
ArgumentNullException.ThrowIfNull(serviceProvider, nameof(serviceProvider));
ServiceProvider = serviceProvider;
}
/// <summary>
/// Form サービスの取得
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static Form GetFormService(Type type)
{
var service = ServiceProvider.GetRequiredService(type);
if (service is Form fService)
{
return fService;
}
else
{
throw new ArgumentException($"{type.FullName} is not a Form");
}
}
/// <summary>
/// サービスの取得
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static T GetService<T>() where T : class
{
return ServiceProvider.GetRequiredService<T>();
}
}
Program メソッドの修正
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main()
{
//.net6 の記法 以前は3行を1行にまとめていた
ApplicationConfiguration.Initialize();
//サービスコンテナの作成
var services = new ServiceCollection();
//サービス登録の追加
ConfigureServices(services);
//ServiceProvider オブジェクトの構築
ServiceProviderHelper.InitServiceProvider(services.BuildServiceProvider());
//指定サービスの取得
var main = ServiceProviderHelper.ServiceProvider.GetRequiredService<Form1>();
Application.Run(main);
}
/// <summary>
/// サービスの注入
/// </summary>
/// <param name="services"></param>
public static void ConfigureServices(IServiceCollection services)
{
//一括注入には Scrutor を使用するか、自分でカプセル化する
services.AddScoped<IUserservice, UserService>();
services.AddScoped<IOrderService, OrderService>();
//その他のフォームもここで注入可能
services.AddSingleton(typeof(Form1));
services.AddTransient(typeof(Form2));
}
}
Form1 と Form2 にそれぞれ注入
private readonly IUserservice _userservice;
public Form1(IUserservice userservice)
{
InitializeComponent();
_userservice = userservice;
}
private readonly IOrderService _orderService;
public Form2(IOrderService orderService) : this()
{
_orderService = orderService;
}
Form1 のボタンクリックで Form2 を表示
private void button1_Click(object sender, EventArgs e)
{
var form2 = ServiceProviderHelper.GetFormService(typeof(Form2));
form2.Show();
}
正常に数回操作しても特に異常は見られませんでした。
7. 参考資料
.NET Core3.1 上で Winform に基づく依存性注入の実装例: http://www.ty2y.com/study/znetcore3.1sjywinformsxylzrsl.html