さあ、Winformで依存性注入を使おう!

さあ、Winformで依存性注入を使おう!

依存性注入とは何か?依存性注入は具体的なコーディング手法であり、私にとって最も顕著なのはコードの結合度を解決することです。

最終更新 2022/04/22 7:21
AZRNG 鹏祥
読了目安 3 分
カテゴリ
Winform
タグ
.NET Winform 依存性注入 IOC

サイト管理者: 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

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2024/02/29

Winformでもこんなデータ表示ができる

winform開発の過程で、データ表示機能が必要になることがよくあります。これまではgridcontrolコントロールを使用していましたが、今日は例を通して、winform blazor hybridでant design blazorのtableコンポーネントを使ってデータ表示を行う方法を紹介します。

続きを読む
同じカテゴリ / 同じタグ 2024/02/29

Winformの画面も綺麗にできる?

先日、winformでblazor hybridを使用することを紹介しました。また、blazorのUIを組み合わせることでwinformプログラムのデザインをより美しくできると言いました。今回はwinform blazor hybridで描画する例を挙げて説明します。参考になれば幸いです。

続きを読む