具有以下服务构造函数

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

使用。net核心IOC机制传递参数的选择是什么

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

还有别的办法吗?


当前回答

如果您对新服务感到不舒服,您可以使用参数对象模式。

因此,将字符串形参提取为它自己的类型

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>();

第一个好处是,如果您需要更改服务构造函数并向其添加新的服务,那么您不必更改新的服务(…调用。另一个好处是设置更干净一些。

对于一个只有一个或两个形参的构造函数,这可能太多了。

其他回答

工厂委托的表达式参数(在本例中为x)是一个IServiceProvider。

使用它来解析依赖项:

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

工厂委托是延迟调用。无论何时要解析该类型,它都会将已完成的提供程序作为委托参数传递。

如果您对新服务感到不舒服,您可以使用参数对象模式。

因此,将字符串形参提取为它自己的类型

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>();

第一个好处是,如果您需要更改服务构造函数并向其添加新的服务,那么您不必更改新的服务(…调用。另一个好处是设置更干净一些。

对于一个只有一个或两个形参的构造函数,这可能太多了。

实现这一点的推荐方法是使用Options模式——注意,这适用于任何。net Core/5应用程序,而不仅仅是ASP。净的核心。但也有一些用例是不切实际的(例如,当参数只在运行时知道,而不是在启动/编译时知道),或者你需要动态替换依赖项。

当您需要替换单个依赖项(字符串、整数或其他类型的依赖项)或使用仅接受字符串/整数参数且需要运行时参数的第三方库时,这非常有用。

你可以试试ActivatorUtilities。CreateInstance<T>(IServiceProvider, Object[])作为快捷方式,而不是手动解决每个依赖项:

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

传递给服务构造函数的参数(object[]参数给CreateInstance<T>/CreateInstance)允许你指定应该直接注入的参数,而不是从服务提供者解析。它们在出现时从左到右应用(即第一个字符串将被替换为要实例化的类型的第一个字符串类型参数)。

ActivatorUtilities。CreateInstance<Service>在许多地方用于解析服务并替换此单个激活的默认注册之一。

例如,如果你有一个名为MyService的类,它有IOtherService, ILogger<MyService>作为依赖项,你想解析这个服务,但是用OtherServiceB替换IOtherService的默认服务(说它是OtherServiceA),你可以这样做:

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

然后IOtherService的第一个参数将注入OtherServiceB,而不是OtherServiceA——但其余参数将来自服务提供者。

当你有很多依赖关系,并且只想特别处理其中一个依赖关系时,这是很有帮助的(例如,用在请求期间或为特定用户配置的值替换特定于数据库的提供程序,这是你只在运行时和/或请求期间知道的,而不是在应用程序构建/启动时知道的)。

如果性能是一个问题,您可以使用ActivatorUtilities。CreateFactory(Type, Type[])来创建工厂方法。GitHub参考和基准。

当类型解析非常频繁时(例如在SignalR和其他高请求场景中),这非常有用。基本上,您可以通过

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new Type[] { 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 string HelloWorld()
    {
        return this.helloWorldService.Hello(firstname, 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);
}

打印

输出:Hello Tseng Stackoverflow

您还可以使用此流程注入依赖项

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