Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

Windows Forms で DI コンテナを使用する際の ShowDialog() 実装例

0
Posted at

はじめに

備忘録です。Windows Forms で DI コンテナを使用する際の ShowDialog 実装例を作ってみました。

改訂履歴

  • 2026/05/12 : 初版公開。

本文

1. 環境

  • Visual Studio Community 2022
  • .NET Framework 4.8.1
  • Microsoft.Extensions.DependencyInjection 10.0.7

2. 実装

Windows Forms と DI のライフサイクルは必ずしも一致しないため、画面遷移は IFormFactory を使用して行います。ファクトリー側で Scope を生成し、HandleDestroyed または Disposed イベントで道連れにすることで、1 つの画面表示 (Open から Close まで) を 1 つの HTTP リクエストに見立てて再現しています。

FormFactory.cs
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;

namespace WindowsFormsDiExample
{
    /// <summary>
    /// <see cref="Form"/> インスタンスを生成するファクトリーを表します。
    /// </summary>
    public interface IFormFactory
    {
        /// <summary>
        /// 指定した <see cref="Form"/> 型のインスタンスを生成します。
        /// </summary>
        /// <typeparam name="T">生成する <see cref="Form"/> 型。</typeparam>
        /// <returns>生成された <see cref="Form"/> インスタンス。</returns>
        T Create<T>() where T : Form;
    }

    /// <summary>
    /// <see cref="Form"/> インスタンスの生成を提供します。
    /// </summary>
    public class FormFactory : IFormFactory
    {
        private readonly IServiceProvider _provider;

        /// <summary>
        /// <see cref="FormFactory"/> の新しいインスタンスを生成します。
        /// </summary>
        /// <param name="provider"><see cref="IServiceProvider"/> オブジェクト。</param>
        public FormFactory(IServiceProvider provider)
        {
            // 本クラスでは意図的に Service Locator パターンを採用している。
            _provider = provider;
        }

        /// <inheritdoc/>
        public T Create<T>() where T : Form
        {
            IServiceScope scope = null;
            var isDisposed = 0;

            try
            {
                // Form のライフサイクルに対応する専用のスコープを生成する。
                scope = _provider.CreateScope();

                // Form インスタンスを生成する。
                T form = scope.ServiceProvider.GetRequiredService<T>();

                // Form の HandleDestroyed または Dispose でスコープを破棄する。
                form.HandleDestroyed += (s, e) => DisposeScope<T>(scope, ref isDisposed);
                form.Disposed += (s, e) => DisposeScope<T>(scope, ref isDisposed);

                return form;
            }
            catch
            {
                // 例外が発生した場合、スコープを破棄する。
                DisposeScope<T>(scope, ref isDisposed);
                throw;
            }
        }

        /// <summary>
        /// 指定したスコープを破棄します。
        /// </summary>
        /// <param name="scope">破棄する <see cref="IServiceScope"/> オブジェクト。</param>
        /// <param name="disposed">スコープが既に破棄されているかどうかを示す値。</param>
        private void DisposeScope<T>(IServiceScope scope, ref int disposed)
        {
            // 未処理の場合、スコープを破棄する。
            if (Interlocked.Exchange(ref disposed, 1) == 0)
            {
                scope.Dispose();
                Debug.Print($"Scope for '{typeof(T).Name}' disposed.");
            }
        }
    }
}

次に Program.cs の実装例です。Form のライフサイクルと同期させたいクラスには AddScoped を使用します。

Program.cs
using System;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;

namespace WindowsFormsDiExample
{
    internal static class Program
    {
        /// <summary>
        /// アプリケーションのメインエントリポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            using (ServiceProvider sp = BuildServiceCollection())
            {
                Form1 mainForm = sp.GetRequiredService<Form1>();
                Application.Run(mainForm);
            }
        }

        /// <summary>
        /// DI コンテナを構築し、必要なサービスを登録します。
        /// </summary>
        /// <returns>構築された <see cref="IServiceProvider"/> オブジェクト。</returns>
        private static ServiceProvider BuildServiceCollection()
        {
            var services = new ServiceCollection();

            // Form
            services.AddTransient<Form1>();
            services.AddTransient<Form2>();
            services.AddTransient<Form3>();

            // Service
            services.AddScoped<IUserUseCase, UserUseCase>();
            services.AddScoped<IDbSession, DbSession>();

            // Factory
            services.AddSingleton<IFormFactory, FormFactory>();
            services.AddSingleton<IDbSessionFactory, DbSessionFactory>();

            return services.BuildServiceProvider();
        }
    }
}

画面に注入する実装例です。Form を生成する場合は using で囲って ShowDialog() を呼び出します。

Form1.cs
using System;
using System.Windows.Forms;

namespace WindowsFormsDiExample
{
    public partial class Form1 : Form
    {
        private readonly IFormFactory _formFactory;

        public Form1(IFormFactory formFactory)
        {
            InitializeComponent();
            _formFactory = formFactory;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            using (Form2 form2 = _formFactory.Create<Form2>())
            {
                form2.ShowDialog();
            }
        }
    }
}

おわりに

Windows Forms でも DI を使っていきましょう。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?