简体   繁体   中英

How registering open-generic types work in IOptions

Below is a very common example that shows the use of IOptions pattern:

public class MyService {
   private readonly MyApiSettings _settings;

   public MyService(IOptions<MyApiSettings> options) {
      _settings = options.Value;
   }
}

// startup.cs
public void ConfigureServices(IServiceCollection services) {
   services.Configure<MyApiSettings>(Configuration.GetSection("MyApi"));  
}

Below is some source code:

public static class OptionsServiceCollectionExtensions { 
   ...
   public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions) {
      services.AddOptions();
      services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
      return services;
   }
   
   public static IServiceCollection AddOptions(this IServiceCollection services) {
      services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));        
      services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));    
      ...
      return services;
   }
}

You can see that the source code only register open-type generic IOptions<> , how could it work? When I learnt Dependency Injection, I was told that I need to registe close-type generic, that's why sometimes we uses third party DI like autofac to simply register open-type generic types. so shouldn't the source code do sth like:

public static IServiceCollection AddOptions<TOptions>(this IServiceCollection services) {
      services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<TOptions>), typeof(OptionsManager<TOptions>)));         
      ...
      return services;
   }

I'm confused, is my understanding of registering generic types wrong or the source code uses some tricks?

Registration of open-generic types is a form of Auto-Registration (aka Batch-Registration). This is similar to how assembly scanning is used to register many types at once, but now many closed versions of the same open-generic type are registered. Note that the Microsoft built-in DI Container, does not have out-of-the-box support for assembly scanning.

The Auto-Registration process of open-generic types differs from assembly scanning in that assembly scanning makes (closed) registrations upfront, while closed types resolved based on an open-generic registration are lazy in nature. This means that the registration process of the closed type only gets triggered when that closed type is first resolved from the Container.

DI Containers implement this lazitly, because it is typically impossible for the Container to figure out which closed types the application might ask for in advance and it is impossible to register all possible closed types for a given open-generic type. For instance, let's say you registered an ILogger<T> . Its generic type argument T can be any possible (non-static) .NET type, including types from assemblies that haven't—yet—been loaded.

This makes the registration of an open-generic type special compared to normal AddTransient<A, B>() registrations, in the sense that there won't exist a direct mapping between the registered open-generic type and an constructable implementation. For instance, you might register AddTransient(typeof(ILogger<>), typeof(Logger<>)) , thus ILogger<T> with an implementation of Logger<T> , but its impossible to create an Logger<T> . You can, on the other hand, construct a Logger<object> or Logger<string> , but not Logger<T> as it is open-generic.

This means that, under the covers, the DI Container is able to 'redirect' the request for ILogger<object> to its generic sub system, which will figure out which exact implementation for Logger<T> matches that of the ILogger<object> request. Because the mapping between the two can become very complex especially because of the existence of generic type constraints, this sub system is very sophisticated in some of the DI Containers. Once the DI Container constructed the correct closed-generic type, it will usually cache this type and the construction of it to improve future look-ups of that same closed-generic abstraction. But when a request for a different closed version—say ILogger<string> —comes in, the lookup process starts all over again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM