.NET Core DI, ways of passing parameters to constructor

后端 未结 3 1083
刺人心
刺人心 2020-12-02 08:28

Having the following service constructor

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


        
3条回答
  •  悲&欢浪女
    2020-12-02 08:47

    It should be noted that the recommended way is to use the Options Pattern. But there are use cases where its unpractical (when parameters are only know at runtime, not at startup/compile time) or you need to dynamically replace a dependency.

    Its very useful when you need to replace a single dependency (be it a string, integer or another type of dependency) or when using a 3rd party library which accepts only string/integer parameters and you require runtime parameter.

    You could try CreateInstance(IServiceProvider, Object[]) as a shortcut hand (not sure it works with string parameters/value types/primitives (int, float, string), untested) (Just tried it out and confirmed its working, even with multiple string parameters) rather than resolving every single dependency by hand:

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

    The parameters (last parameter of CreateInstance/CreateInstance) define the parameters which should be replaced (not resolved from the provider). They are applied from left to right as they appear (i.e. first string will be replaced with the first string-typed parameter of the type to be instantiated).

    ActivatorUtilities.CreateInstance is used on many places to resolve a service and replace one of the default registrations for this single activation.

    For example if you have a class named MyService, and it has IOtherService, ILogger as dependencies and you want to resolve the service but replace the default service of IOtherService (say its OtherServiceA) with OtherServiceB, you could do something like:

    myService = ActivatorUtilities.CreateInstance(serviceProvider, new OtherServiceB())
    

    Then the first parameter of IOtherService will get OtherServiceB injected, rather than OtherServiceA but the remaining parameters will come from the container.

    This is helpful when you have a lot dependencies and want just to specially treat a single one (i.e. replace a database specific provider with a value configured during the request or for a specific user, something you only know at run time and during a request and not when the application is built/started).

    You could also use ActivatorUtilities.CreateFactory(Type, Type[]) Method to create factory method instead, since it offers better performance GitHub Reference and Benchmark.

    Later one is useful when the type is resolved very frequently (such as in SignalR and other high request scenarios). Basically you'd create a ObjectFactory via

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

    then cache it (as a variable etc) and call it where needed

    MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);
    

    ##Update: Just tried it myself to confirm its also working with strings and integers, and it does indeed work. Here the concrete example I tested with:

    class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection();
            services.AddTransient();
            services.AddTransient(p => p.ResolveWith("Tseng", "Stackoverflow"));
    
            var provider = services.BuildServiceProvider();
    
            var demoService = provider.GetRequiredService();
    
            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(this IServiceProvider provider, params object[] parameters) where T : class => 
            ActivatorUtilities.CreateInstance(provider, parameters);
    }
    

    Prints

    Output: Hello Tseng Stackoverflow
    

提交回复
热议问题