はじめに
備忘録です。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 を使っていきましょう。