web-dev-qa-db-ja.com

.NET Core DI、コンストラクターにパラメーターを渡す方法

次のサービスコンストラクターを持つ

public class Service : IService
{
     public Service(IOtherService service1, IAnotherOne service2, string arg)
     {

     }
}

.NET Core IOCメカニズムを使用してパラメーターを渡す選択肢は何ですか

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( _serviceCollection.BuildServiceProvider().GetService<IOtherService>(), _serviceCollection.BuildServiceProvider().GetService<IAnotherOne >(), "" ));

他の方法はありますか?

39
MCR

ファクトリーデリゲートの式パラメーター(x)は、IServiceProviderです。

それを使用して依存関係を解決し、

_serviceCollection.AddSingleton<IService>(x => 
    new Service(x.GetRequiredService<IOtherService>(),
                x.GetRequiredService<IAnotherOne>(), 
                ""));

ファクトリデリゲートは遅延呼び出しです。型が解決されるときはいつでも、デリゲートパラメーターとして完成したプロバイダーを渡します。

41
Nkosi

推奨される方法は Options Pattern を使用することです。ただし、非実用的(起動時/コンパイル時ではなく、実行時のみパラメータがわかっている場合)または依存関係を動的に置き換える必要があるユースケースがあります。

単一の依存関係(文字列、整数、または別の種類の依存関係)を置き換える必要がある場合、または文字列/整数パラメーターのみを受け入れ、ランタイムパラメーターが必要なサードパーティライブラリを使用する場合に非常に便利です。

CreateInstance(IServiceProvider、Object []) をショートカットとして試すことができます (文字列パラメーター/値型/プリミティブ(int、float、string)、未テストで動作するかどうかはわかりません) (複数の文字列パラメータでも試してみて、動作を確認しただけです)すべての単一の依存関係を手動で解決するのではなく:

_serviceCollection.AddSingleton<IService>(x => 
    ActivatorUtilities.CreateInstance<Service>(x, "");
);

パラメーター(CreateInstance<T>/CreateInstanceの最後のパラメーター)は、置き換えられるべきパラメーターを定義します(プロバイダーから解決されない)。それらは、表示されるときに左から右に適用されます(つまり、最初の文字列は、インスタンス化される型の最初の文字列型パラメーターに置き換えられます)。

ActivatorUtilities.CreateInstance<Service>は、サービスを解決し、この単一のアクティベーションのデフォルト登録の1つを置き換えるために多くの場所で使用されます。

たとえば、MyServiceという名前のクラスがあり、依存関係としてIOtherServiceILogger<MyService>があり、サービスを解決したいがIOtherServiceのデフォルトサービスを置き換えたい場合OtherServiceA)とOtherServiceBを使用すると、次のようなことができます。

myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())

その後、IOtherServiceの最初のパラメーターは、OtherServiceBではなくOtherServiceAが注入されますが、残りのパラメーターはコンテナーから取得されます。

これは、多くの依存関係があり、単一の依存関係を特別に扱いたい場合に役立ちます(つまり、データベース固有のプロバイダーを、要求時または特定のユーザー用に構成された値に置き換えます。アプリケーションがビルド/開始されたときではありません)。

代わりに ActivatorUtilities.CreateFactory(Type、Type [])Method を使用して、ファクトリメソッドを作成することもできます。パフォーマンスが向上するためです GitHubリファレンス および Benchmark =。

タイプが非常に頻繁に解決される場合(SignalRやその他の要求の多いシナリオなど)、後の方法が役立ちます。基本的にはObjectFactoryを作成します

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });

次に(変数などとして)キャッシュし、必要な場所で呼び出します

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

更新:

文字列と整数でも動作することを確認するために自分で試してみましたが、実際に動作します。ここで私がテストした具体的な例:

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddTransient<HelloWorldService>();
        services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow"));

        var provider = services.BuildServiceProvider();

        var demoService = provider.GetRequiredService<DemoService>();

        Console.WriteLine($"Output: {demoService.HelloWorld()}");
        Console.ReadKey();
    }
}

public class DemoService
{
    private readonly HelloWorldService helloWorldService;
    private readonly string firstname;
    private readonly string lastname;

    public DemoService(HelloWorldService helloWorldService, string firstname, string lastname)
    {
        this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService));
        this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname));
        this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname));
    }

public class HelloWorldService
{
    public string Hello(string name) => $"Hello {name}";
    public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}";
}

// Just a helper method to shorten code registration code
static class ServiceProviderExtensions
{
    public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => 
        ActivatorUtilities.CreateInstance<T>(provider, parameters);
}

プリント

Output: Hello Tseng Stackoverflow
18
Tseng

サービスの新規作成に不安を感じた場合は、Parameter Objectパターンを使用できます。

したがって、文字列パラメータを独自のタイプに抽出します

public class ServiceArgs
{
   public string Arg1 {get; set;}
}

そして、コンストラクターは次のようになります

public Service(IOtherService service1, 
               IAnotherOne service2, 
               ServiceArgs args)
{

}

そしてセットアップ

_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; });
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService, Service>();

最初の利点は、Serviceコンストラクターを変更して新しいサービスを追加する必要がある場合、new Service(...呼び出しを変更する必要がない場合です。もう1つの利点は、セットアップが少しきれいになることです。

ただし、1つまたは2つのパラメーターを持つコンストラクターでは、これは多すぎる可能性があります。

6
Adrian Iftode