Article From:https://www.cnblogs.com/micro-chen/p/9970738.html
In the process of learning Asp.Net Core, injection can be said to be everywhere. For. Net Core, it is a separate assembly without complex dependencies and configuration files, so for friends who learn Asp.Net Core source code.Said that injection is a good starting point, there are many blogs about injection in the garden, but. Net Core 2.0 has come out, injection has made some updates, in fact, there are many. net developers.MicrosoftIt is not very satisfactory to change this point, which increases the cost of learning. In fact, there are two kinds of changes. One is the change of the commonly used Api interface (or configuration change) of Asp.Net Core Mvc. This has rarely happened since 2.0, that is to say, As.The p.Net Core Mvc is basically stable, and the other is code optimization. The former is harmful to R&D follow-up, while the latter is not important to R&D, and may bring a lot of thinking to programmers who are willing to learn source code.
  So I’m going to re-analyze the injection of. Net Core 2.0. The actual release version is. netstandard 2.0 and the assembly is Microsoft. Extensions. Dependency Injection. dl.L.
  In. Net Core, injection is described as three processes: registering service – & gt; creating container – & gt; and creating objects, so I will also introduce them in three modules.
  Injecting metadata
  If you’ve touched. Net Core, you’ve more or less touched injection. The following code registers services with three lifecycles, then creates a container, and finally uses the container to provide instance objects of these three services. We look at their lifecycles and see that the output is basically to A.The life cycle of services registered by ddTransient and AddSingleton will be judged, while services registered by AddScoped are more complex.
  We see that a container is created through the BuilderService Provider method, and two containers with scope can be created by calling CreateScope, while services registered by AddScoped are in different scope.The life cycle is different, and the life cycle in the same scope is the same as that in Add Singleton.
  interface ITransient { }
  class Transient : ITransient { }
  interface ISingleton { }
  class Singleton : ISingleton { }
  interface IScoped { }
  class Scoped : IScoped { }
  class Program
  {
      static void Main(string[] args)
      {
          IServiceCollection services = new ServiceCollection();
          services = services.AddTransient<ITransient, Transient>();
          services = services.AddScoped<IScoped, Scoped>();
          services = services.AddSingleton<ISingleton, Singleton>();
          IServiceProvider serviceProvider = services.BuildServiceProvider();
           
          Console.WriteLine(ReferenceEquals(serviceProvider.GetService<ITransient>(), serviceProvider.GetService<ITransient>()));
          Console.WriteLine(ReferenceEquals(serviceProvider.GetService<IScoped>(), serviceProvider.GetService<IScoped>()));
          Console.WriteLine(ReferenceEquals(serviceProvider.GetService<ISingleton>(), serviceProvider.GetService<ISingleton>()));
          IServiceProvider serviceProvider1 = serviceProvider.CreateScope().ServiceProvider;
          IServiceProvider serviceProvider2 = serviceProvider.CreateScope().ServiceProvider;
          Console.WriteLine(ReferenceEquals(serviceProvider1.GetService<IScoped>(), serviceProvider1.GetService<IScoped>()));
          Console.WriteLine(ReferenceEquals(serviceProvider1.GetService<IScoped>(), serviceProvider2.GetService<IScoped>()));
          Console.WriteLine(ReferenceEquals(serviceProvider1.GetService<ISingleton>(), serviceProvider2.GetService<ISingleton>()));
          /* False
           * True
           * True
           * True
           * False
           * True
           */
      }
  }
  IServiceCollection
  public interface IServiceCollection : IList<ServiceDescriptor>
  {
  }
  Is a collection of service metadata used to store user registration
  ServiceDescriptor
  Looking at the example above, we can guess what attributes the Service Descriptor contains by adding injection. It contains at least one interface type, implementation type, and lifecycle, and that’s it.
  public class ServiceDescriptor
  {
      public ServiceLifetime Lifetime { get; }
      public Type ServiceType { get; }
      public Type ImplementationType { get; }
      public object ImplementationInstance { get; }
      public Func<IServiceProvider, object> ImplementationFactory { get; }
  }
  In the first code block, the IServiceCollection signature extension method is used to register the service. Here I call it “service type instance type” (providing a service type, an instance type), the corresponding service type and instance class.ServiceType, Implementation Instance passed to Service Descriptor by parsing generic parameters. It is noteworthy that creating Service Descriptor does not verifyCreability of instance types (verify that they are abstract classes, interfaces)?
  public static IServiceCollection AddTransient<TService, TImplementation>(this IServiceCollection services)
      where TService : class
      where TImplementation : class, TService
  {
      if (services == null)
      {
          throw new ArgumentNullException(nameof(services));
      }
      return services.AddTransient(typeof(TService), typeof(TImplementation));
  }
  In addition, Microsoft also provides “service instance” (providing a service type, an instance object) and “service instance factory” (providing a service type, an instance object factory) registration mode. The former is only for single service use, and it is very simple to use.
  services.AddTransient<ITransient>(_=>new Transient());
  services.AddSingleton<ISingleton>(new Singleton());
  One more thing about Service Descriptor is the service life cycle. Services registered in three ways, AddSingleton, AddScoped and AddTransient, are registered in Service Desc.LifeTime attributes in riptor correspond to the following enumeration types
  public enum ServiceLifetime
  {
      Singleton,
      Scoped,
      Transient
  }
  1、Transient:Each time it is retrieved from an IServiceProvider, it is a new instance
  2、Singleton:Each time it is retrieved from the same root container (the same root IServiceProvider), it is the same instance.
  3、Scoped:The instances obtained from the same container are the same each time.
  It doesn’t matter if it’s not clear about the life cycle of a service, because we’ll continue to learn about it.
  Customize the process of creating containers and objects
  stayArticleAt the beginning, we introduce the three processes of the injection framework, registration service – & gt; creation container – & gt; creation of objects. However, the steps of registration service are very simple, which provide generic parameters by a method similar to AddTransient and AddSingleton.Or is it as simple as converting arguments into a Service Descriptor object stored in the IServiceCollection and creating containers and bed objects? If so, it must be easy to write the following code
  public class MyServiceProvider : IServiceProvider
  {
      private List<ServiceDescriptor> serviceDescriptors = new List<ServiceDescriptor>();
      private Dictionary<Type, object> SingletonServices = new Dictionary<Type, object>();
      public MyServiceProvider(IEnumerable<ServiceDescriptor>  serviceDescriptors)
      {
          this.serviceDescriptors.AddRange(serviceDescriptors);
      }
      public object GetService(Type serviceType)
      {
          var descriptor = serviceDescriptors.FirstOrDefault(t => t.ServiceType == serviceType);
          if(descriptor == null)
          {
              throw new Exception($”Service'{serviceType. Name}’is not registered.
          }
          else
          {
              switch (descriptor.Lifetime)
              {
                  case ServiceLifetime.Singleton:
                      if (SingletonServices.TryGetValue(descriptor.ServiceType,out var obj))
                      {
                          return obj;
                      }
                      else
                      {
                          var singletonObject = Activator.CreateInstance(descriptor.ImplementationType);
                          SingletonServices.Add(descriptor.ServiceType, singletonObject);
                          return singletonObject;
                      }
                  case ServiceLifetime.Scoped:
                      throw new NotSupportedException($”Creation failed, Scoped is not supported for the time being.
                  case ServiceLifetime.Transient:
                      var transientObject = Activator.CreateInstance(descriptor.ImplementationType);
                      return transientObject;
                  default:
                      throw new NotSupportedException(“Create failed, unrecognizable LifeTime “;
              }
          }
      }
  }
  public static class ServiceCollectionContainerBuilderExtensions
  {public static MyServiceProvider BuildeMyServiceProvider(this IServiceCollection services)
      {
          return new MyServiceProvider(services);
      }
  }
  Because of the particularity of Scoped, some people stop writing here. However, there is another problem. We know that there may be many ways to register services. Here, we only give the “service instance type” and make some modifications.
  case ServiceLifetime.Singleton:
      if (SingletonServices.TryGetValue(descriptor.ServiceType,out var obj))
      {
          return obj;
      }
      else
      {
          if(descriptor.ImplementationType != null)
          {
              var singletonObject = Activator.CreateInstance(descriptor.ImplementationType);
              SingletonServices.Add(descriptor.ServiceType, singletonObject);
              return singletonObject;
          }
          else if(descriptor.ImplementationInstance != null)
          {
              SingletonServices.Add(descriptor.ServiceType, descriptor.ImplementationInstance);
              return descriptor.ImplementationInstance;
          }
          else if(descriptor.ImplementationFactory != null)
          {
              var singletonObject = descriptor.ImplementationFactory.Invoke(this);
              SingletonServices.Add(descriptor.ServiceType, singletonObject);
              return singletonObject;
          }
          else
          {
              throw new Exception(“The creation of the service failed and the instance type or instance could not be found.
          }
      }
  Although Singleton is only rewritten here, so should others, which can actually be written all the time, it’s not elegant as a C# developer because it’s a process-oriented (or object-based) open development model.
  In addition, Microsoft’s injection does not support attribute injection, but remember, it still supports constructor injection, otherwise this injection would be too helpful! Yes, according to the above code snippet, we can continue to write. When we parse the instance type, we find its constructor and find its constructor.It is a recursive process that all parameters of a function are created in the same way. Finally, callbacks can still create the objects we need. But how can all these be implemented robustly and elegantly? This is the reason for learning the source code.
  How does Microsoft further process metadata?
  In fact, the main problem with the above code is that the process of creating containers and objects is over-coupled, and there is one of the biggest problems. Think carefully about the ServiceDescriptor every time an object is created to determine that it is “service instance type”, “service instance type”, “service instance type”, “service description” and “service instance type”.Which way is registered in the Service Instance Object and the Service Instance Object Factory? This leads to some unnecessary performance consumption. However, this work was done by Microsoft when creating containers. Following the process of creating containers, we have no hesitation to go to the source code! Where to? Finding Microsoft and HowService Descriptor!
  The first obstacle we encounter here is Service Provider. The container we create is ultimately a type like this. See how it creates objects?
  public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback
  {
      private readonly IServiceProviderEngine _engine;
      internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
      {
          //Some code is omitted here
          switch (options.Mode)
          {
              case ServiceProviderMode.Dynamic:
                  _engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
                  break;
              //Some code is omitted here
              default:
                  throw new ArgumentOutOfRangeException(nameof(options.Mode));
          }
      }
      public object GetService(Type serviceType) => _engine.GetService(serviceType);
      public void Dispose() => _engine.Dispose();
  }
  Here we know that the ultimate provider object is not a Service Provide, but a field _engine type of IServiceProviderEngine. In the switch statement, I only post Dynam.The code for this branch of ic, because the default value of the enumeration variable options is always Dynamic, we just need to know that the core of the object provided in Service Provider is a Service Provider Engi.Ne, and its default instance is a DynamicService Provider Engine, because this expedition is to analyze how Microsoft handles metadata. All this must be in Dynamic Service Provider EngThe ine creation process is complete, so we just look for its constructor, and finally we find it in the parent ServiceProviderEngine!
  internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory
  {
      internal CallSiteFactory CallSiteFactory { get; }
      protected ServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback)
      {
          //Some code has been omitted
          CallSiteFactory = new CallSiteFactory(serviceDescriptors);
          CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
          CallSiteFactory.Add(typeof(IServiceScopeFactory), new ServiceScopeFactoryCallSite());
      }
  }
  CallSiteFactory
  There are only three fields in this category posted here, but there are only three fields in this type. If the specific functions of these three fields are understood, the answer to the question of how Microsoft handles metadata will be known.
  internal class CallSiteFactory
  {
      private readonly List<ServiceDescriptor> _descriptors;
      private readonly Dictionary<Type, IServiceCallSite> _callSiteCache = new Dictionary<Type, IServiceCallSite>();
      private readonly Dictionary<Type, ServiceDescriptorCacheItem> _descriptorLookup = new Dictionary<Type, ServiceDescriptorCacheItem>();
      private struct ServiceDescriptorCacheItem
      {
          private ServiceDescriptor _item;
          private List<ServiceDescriptor> _items;
          //Some code has been omitted
      }
  }
  internal interface IServiceCallSite
  {
      Type ServiceType { get; }
      Type ImplementationType { get; }
  }
   The first field _descriptors is a metadata set, where all the services we registered are, and then we look at the third field _descriptorLookup, because the first one when registering a service does not verify the validity of the instance type (interface, Abstract class).In addition, we can register multiple copies of the same service. How can Microsoft determine the object of creation for multiple registered services? For these problems, Microsoft designed a class that summarizes all registered instance types of a specific service, Service Descriptor C.AcheItem, specifically for a service, the first registered metadata exists in _item, and all metadata of the subsequent service exists in _items, and the default always identifies with the last metadata. Finally, the hardest thing to understand is _callSiteCacheThis field, in short, its value IServiceCallSite is the basis for creating service instances, including service types and instance types. We know that what we get from _descriptorLookup is a definite instance type, but this instance classHow to create the type in the constructor of type I, which is embodied in IServiceCallSite. Since IServiceCallSite is the basis for creating instances, by observing the definition of this interface, it is found that there are no attributes related to the life cycle.A little disappointed!
  Let’s go back to the line where the Service Provider Engine created the CallSiteFactory. When the CallSiteFactory was created, it called the Add method and added two key-value pairs. firstWhat are the keys for the line code? IServiceProvider is Microsoft’s default allowing IServiceProvider to provide itself!
  CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
  CallSiteFactory.Add(typeof(IServiceScopeFactory), new ServiceScopeFactoryCallSite());
  You can see that the key-value pairs added by Add are stored in _callSiteCache
  public void Add(Type type, IServiceCallSite serviceCallSite)
  {
      _callSiteCache[type] = serviceCallSite;
  }
  Then we look at the two types of Service Provider CallSite and Service Scope Factory CallSite, adding two unknown types with no other gains.
  internal class ServiceProviderCallSite : IServiceCallSite
  {
      public Type ServiceType { get; } = typeof(IServiceProvider);
      public Type ImplementationType { get; } = typeof(ServiceProvider);
  }
  internal class ServiceScopeFactoryCallSite : IServiceCallSite
  {
      public Type ServiceType { get; } = typeof(IServiceScopeFactory);
      public Type ImplementationType { get; } = typeof(ServiceProviderEngine);
  }
  Some conjectures about injection
  From the above learning we have a more unexpected gain, IServiceProvider can provide their own, which has to make us wonder, IServiceProvider has what kind of life cycle? If you keep using an IServiceProvider creates a new one. What if it goes on like that?
  static void Main(string[] args)
  {
      IServiceCollection services = new ServiceCollection();
      var serviceProvider = services.BuildServiceProvider();
      Console.WriteLine(ReferenceEquals(serviceProvider.GetService<IServiceProvider>(), serviceProvider.GetService<IServiceProvider>()));
      var serviceProvider1 = serviceProvider.CreateScope().ServiceProvider;
      var serviceProvider2 = serviceProvider.CreateScope().ServiceProvider;
      Console.WriteLine(ReferenceEquals(serviceProvider1.GetService<IServiceProvider>(), serviceProvider2.GetService<IServiceProvider>()));
      var serviceProvider3 = serviceProvider.GetService<IServiceProvider>();
      var serviceProvider4 = serviceProvider.GetService<IServiceProvider>();
      var serviceProvider3_1 = serviceProvider3.GetService<IServiceProvider>();
      var serviceProvider4_1 = serviceProvider4.GetService<IServiceProvider>();
      Console.WriteLine(ReferenceEquals(serviceProvider3,serviceProvider4));
      Console.WriteLine(ReferenceEquals(serviceProvider3_1, serviceProvider4_1));
      Console.WriteLine(ReferenceEquals(serviceProvider3, serviceProvider3_1));
      Console.WriteLine(ReferenceEquals(serviceProvider3,serviceProvider));
      /* True
       * False
       * True
       * True
       * True
       * False
       */
  }
  Here we just need to know that CreateScope creates a container with a limited scope. We can see the generation of IServiceProvider from this point of view, based on the first output being True and the second output being False.The definition of life cycle and coped is the same, but because of the particularity of IServiceProvider, it can constantly create itself, and they are all the same object, but they are different from the original ServiceProvider. This letsWe wonder if IServiceProvider is Scoped.
  Summary
  This section mainly introduces three life cycles of services and how services are registered with metadata. In the process of creating containers, we know how Microsoft further processes metadata, and the ultimate basis for creating instance objects is IServiceCallSite.To really understand IServiceCallSite, you must also have a detailed understanding of the process of creating containers and instances.

Leave a Reply

Your email address will not be published. Required fields are marked *