rksoftware

Visual Studio とか C# とかが好きです

.NET で DI する

.NET のコンソールアプリで DI する最低限の要素をメモします。

詳しくはこちら

■ コード

やはりコードがわかりやすいと思うので、まずコードを。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((_, services) =>
        // 登録
        services
            .AddSingleton<ISample, Sample>()
            .AddSingleton<IStart, Start>()
    )
    .Build();

// 実行
host.Services.GetService<IStart>()!.StartMethod();

各種インターフェイスと実装

// サンプル依存インターフェイス
interface ISample { void SampleMethod(); }
class Sample : ISample { public void SampleMethod() { Console.WriteLine("Sample"); } }
// 最も浅い階層のクラス (最初に呼ばれるクラス)
interface IStart { void StartMethod(); }
class Start : IStart
{
    ISample sample;
    public Start(ISample sample) => this.sample = sample;
    public void StartMethod() => sample.SampleMethod();
}

実行結果

Sample

■ Run や Start といったカッコよさそうなメソッドは

今回は実行部分を

host.Services.GetService<IStart>()!.StartMethod();

としています。自分で取り出してメソッドを呼んでいるところが少しカッコ悪い気がします。実は IHost には Run メソッドや Start メソッドといったカッコよさそうなメソッドがあるのですが、ちょっと希望と違う感じでした。

Run や Start で呼ばれるクラス

IHostedService インターフェイスの実装として登録しているものの StartAsync メソッドが呼ばれます。複数登録していれば複数呼ばれます。次の例では 3 回登録されているので 3 つが呼ばれています。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((_, services) =>
        // 登録
        services
            .AddSingleton<ISample, Sample>()
            .AddSingleton<IHostedService, Start>()
            .AddSingleton<IHostedService, Start>()
            .AddSingleton<IHostedService, Start>()
    )
    .Build();

// 実行
host.Run();
// サンプル依存インターフェイス
interface ISample { void SampleMethod(); }
class Sample : ISample { public void SampleMethod() { Console.WriteLine("Sample"); } }
// 最も浅い階層のクラス
//interface IStart { void StartMethod(); }
class Start : IHostedService
{
    ISample sample;
    static long total;
    long me;
    public Start(ISample sample) => (this.sample, this.me) = (sample, ++total);
    public Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine(me);
        sample.SampleMethod();
        return Task.CompletedTask;
    }
    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

実行結果

1
Sample
2
Sample
3
Sample
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\XXXXXXXXXX\bin\Debug\net6.0

実行自体はされていますが、コンソールアプリは終了しません。実行結果で表示されているようにユーザーに Ctrl+C が求められます。コンソールアプリとしては処理終了後にアプリケーションが終了して欲しいです。

■ Start の場合

// 実行
host.Run();

// 実行
host.Start();

にしてみると

実行結果

Sample
2
Sample
3
Sample
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\XXXXXXXXXX\bin\Debug\net6.0

C:\XXXXXXXXXX\bin\Debug\net6.0\XXXXXXXXXX.exe (プロセス XXXXX) は、コード 0 で終了しました。
デバッグが停止したときに自動的にコンソールを閉じるには、[ツール] -> [オプション] -> [デバッグ] -> [デバッグの停止時に自 動的にコンソールを閉じる] を有効にします。
このウィンドウを閉じるには、任意のキーを押してください...

アプリケーションが終了しますが、そうじゃないという感じです。

難しいですね。