基础知识 我们如何理解IOC?我们可以通过一个现实世界的模型来进行解释。比如有一本菜谱这个菜谱就是我们的IServiceCollection,里面记录了菜(Service)的描述信息(ServiceDescriptor)菜名(ServiceDescriptor.ServiceType)以及菜具体制作方法(ServiceDescriptor.ImplementationType),通过菜名(ServiceType)告诉厨师(IServiceProvider)制作(实列化、解析)出来我们要吃的菜。这就是IOC技术。
依赖项
Microsoft.Extensions.DependencyInjection.Abstractions:抽象包,用于扩展容器
Microsoft.Extensions.DependencyInjection:实现包,实现IOC的基本功能
核心接口 Service:就是我们需要的服务实列(菜)
ServiceDescriptor:用于描述服务的信息。比如服务名(ServiceType)、实现类(ImplementationType)、生命周期(Lifetime)。(某道菜的制作描述信息)
IServiceCollection:是一个List集合,用于保存服务描述信息。(菜谱,记录了很多菜的描述信息)
IServiceProvider:用于解析服务实列,根容器和子容器实现类不同(厨师)实现类里面有字段用于标记是否是根容器,以及记录所有解析的实列,为将来释放做准备。
ActivatorUtilities:用于解析一个容器中不存在,但是依赖了容器中的服务的实列。
关键字
依赖:如果一个类A的构造器中有一个类B的参数,我们说A依赖B
注入:如果A依赖B,要想实列化A,就必须先实列化B,然后把B载入A的构造器的过程
依赖注入:IOC容器根据反射得到一个类的依赖关系,自动帮你载入依赖项的过程(注意循环依赖问题)
服务注册
服务描述
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ServiceDescriptor { public Type ServiceType { get ; } public Type? ImplementationType { get ; } public ServiceLifetime Lifetime { get ; } public Func<IServiceProvider, object >? ImplementationFactory { get ; } public object ? ImplementationInstance { get ; } }
万能公式
1 2 3 4 5 6 IServiceCollection services = new ServiceCollection(); services.Add(new ServiceDescriptor(typeof (IConnection),typeof (SqlDbConnection),ServiceLifetime.Singleton));
泛型接口
1 2 services.AddSingleton<IDbConnection, SqlDbConnection>();
反射接口
1 2 services.AddSingleton(typeof (IDbConnection), typeof (SqlDbConnection));
委托方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 services.AddSingleton<IDbConnection, SqlConnection>(); services.AddSingleton(sp => { var connection = sp.GetRequiredService<IDbConnection>(); return new DbContext(connection, "c1" ); }); services.AddSingleton(sp => { return ActivatorUtilities.CreateInstance<DbContext>(sp,"c1" ); });
泛型注册
1 2 3 services.AddSingleton(typeof (ILogger<>), typeof (ConsoleLogger<>));
尝试注册
1 2 services.TryAddSingleton(typeof (IDbConnection), typeof (SqlDbConnection));
默认服务
1 2 3 IServiceCollection services = new ServiceCollection(); var sp = services.BuildServiceProvider();var sp1 = sp.GetRequiredService<IServiceProvider>();
构建容器 1 2 3 4 5 IServiceProvider container = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true , ValidateScopes = true , });
服务解析 1 2 3 4 5 6 7 8 9 10 11 12 services.AddSingleton<IDbConnection, SqlConnection>(); services.AddSingleton<IDbConnection, MySqlConnection>(); IServiceProvider container = services.BuildServiceProvider(); IDbConnection? connection = container.GetService<IDbConnection>(); IDbConnection connection = container.GetRequiredService<IDbConnection>(); IEnumerable<IDbConnection> connections = container.GetRequiredServices<IDbConnection>(); var context = ActivatorUtilities.CreateInstance<DbContext>(container, "c1" );
生命周期
容器除了会帮我们创建对象,还负责对象的销毁,特别对于托管资源。
不要试图通过根容器来解析Scoped或者Transient生命周期的实列
单实例的对象不能依赖一个Scoped或者Transient生命周期的实列
在Debug模式下可以看到容器是否是根容器,以及容器解析的实列,容器会记录由它解析的所有实列,为释放做准备。
我们需要会搭建测试案例,来验证是否是同一个实列,以及释放问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class A : IDisposable { public string ID { get ; } public A () { ID = Guid.NewGuid().ToString(); } public void Dispose () { Console.WriteLine(ID + ":已释放..." ); } } var services = new ServiceCollection();services.AddScoped<A>(); IServiceProvider container = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true , ValidateScopes = false , }); var a1 = container.GetRequiredService<A>();var a2 = container.GetRequiredService<A>();using (var scope = container.CreateScope()){ var a3 = scope.ServiceProvider.GetRequiredService<A>(); var a4 = scope.ServiceProvider.GetRequiredService<A>(); Console.WriteLine("scop0:" + a1.ID); Console.WriteLine("scop0:" + a2.ID); Console.WriteLine("scop1:" + a3.ID); Console.WriteLine("scop1:" + a4.ID); }
通过修改A服务注册的生命周期我们可以得到一下结论。
测试Singleton发现:a1,a2,a3,a4的Id都相同 测试Scope发现:a1和a2的Id相同,a3和a4的Id相同,a1和a3的Id不相同 测试Transient发现:a1,a2,a3,a4的Id都不同
Singleton:无论通过根容器还是子容器,获取的都是同一实列,而且不会执行释放(除非释放根容器)。
Scoped:同一scope获取的都是同一实列,不同的scope获取的实列不同。scope释放会释放由它解析出来的所有实列(除了单实例以外),如果并执行Dispose方法(前提实现了IDisposable)。
Transient:无论是否同一scope获取的实列都不同,每次获取都是一个新的实列,scope释放会释放所有的实列。
注意:ServiceProvider会记录由它创建的所有实列,如果释放IServiceScope的实列,则会释放(ServiceProvider)和所有(单实列除外)由它创建的实列。
Scope范围:scope的范围有多大取决于你何时创建何时释放。从创建到释放就是他的生命周期。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class A : IDisposable { public string ID { get ; } public A () { ID = = Guid.NewGuid().ToString(); } public void Dispose () { Console.WriteLine(ID + ":已释放..." ); } } IServiceProvider container = services.BuildServiceProvider(); var scope = rootContainer.CreateScope();var a1 = container.GetRequiredService<A>();Thread.Sleep(5 * 1000 ); scope.Dispose();
组件扫描 组件扫描可以自定义规则,比如根据实现了某个接口,或者统一后缀
这里我们演示如何通过注解来扫描,大家也可以根据接口的方式来扫描
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 [AttributeUsage(AttributeTargets.Class) ] public class InjectionAttribute : Attribute { public Type? ServiceType { get ; set ; } public ServiceLifetime Lifetime { get ; set ; } = ServiceLifetime.Transient; } public static class InjectionIServiceCollectionExtensions { public static IServiceCollection AddServicesByInjection <T >(this IServiceCollection services ) { var serviceTypes = typeof (T).Assembly.GetTypes() .Where(a => a.IsClass) .Where(a => a.GetCustomAttribute<InjectionAttribute>() != null ) .Where(a => !a.IsAbstract); foreach (var item in serviceTypes) { var injection = item.GetCustomAttribute<InjectionAttribute>(); if (injection!.ServiceType == null ) { services.Add(new ServiceDescriptor(item, item, injection.Lifetime)); } else { services.Add(new ServiceDescriptor(injection!.ServiceType, item, injection.Lifetime)); } } return services; } } public interface IDbConnection { } [Injection(ServiceType = typeof(IDbConnection), Lifetime = ServiceLifetime.Scoped) ] public class DbConnection : IDbConnection { } var services = new ServiceCollection();services.AddServicesByInjection<Program>(); var sp = services.BuildServiceProvider();var connection = sp.GetService<IDbConnection>();
构造模式
构造器的目的和构造函数一样,但是构造器可以提供丰富的api来简化对象的构造
构造模式用于简化被构造对象的创建,通过提供一大堆的api来丰富简化构造过程,增加调用者的体验。
构造者需要提供一个Build方法用于构建和返回将要构造的对象实列。
在容器中一般需要提供一个公开的IServiceCollection类型的属性,用于注册服务。
IServiceCollection是构造者模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public enum ServiceLifetime{ Transient, Scoped, } public class ServiceDescriptor { public Type ServiceType { get ; } public ServiceLifetime Lifetime { get ; } public ServiceDescriptor (Type serviceType, ServiceLifetime lifetime ) { ServiceType = serviceType; Lifetime = lifetime; } } public interface IContainer { } public class Container : IContainer { private List<ServiceDescriptor> _services = new (); public Container (List<ServiceDescriptor> services ) { _services = services; } } public interface IContainerBuilder { void Add (ServiceDescriptor descriptor ) ; IContainer Build () ; } public class ContainerBuilder : IContainerBuilder { private List<ServiceDescriptor> _services = new (); public void Add (ServiceDescriptor descriptor ) { _services.Add(descriptor); } public IContainer Build () { return new Container(_services); } } public static class IContainerBuilderExtensions { public static void AddTransient <T >(this IContainerBuilder builder ) { builder.Add(new ServiceDescriptor(typeof (T), ServiceLifetime.Transient)); } public static void AddScoped <T >(this IContainerBuilder builder ) { builder.Add(new ServiceDescriptor(typeof (T), ServiceLifetime.Scoped)); } } var containerBuilder = new ContainerBuilder();containerBuilder.AddScoped<DbContext>(); var container = containerBuilder.Build();
工厂模式
工厂模式侧重于对象的管理(创建销毁),一般提供一个Create方法,支持命名创建。
通过上面的学习我们发现IOC有一个弊端,就是他是通过服务类型的解析服务的。有些情况下我们需要通过命名的方式来解析服务。此时可以使用工厂模式。
IServiceProvider也是工厂模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public interface IDbConnection { } public class MySqlDbConnection : IDbConnection { } public class SqlDbConnection : IDbConnection { } public class DbConnectionFactory { private Dictionary<string , Type> _connections; public DbConnectionFactory (Dictionary<string , Type> connections ) { _serviceProvider = provider; _connections = connections; } public IDbConnection? Create(IServiceProvider serviceProvider, string name) { if (_connections.TryGetValue(name, out Type? connectionType)) { return serviceProvider.GetRequiredService(connectionType) as IDbConnection; } return default ; } } var services = new ServiceCollection();services.AddScoped<MySqlDbConnection>(); services.AddScoped<SqlDbConnection>(); services.AddSingleton(sp => { var connections = new Dictionary<string , Type> { { "s1" , typeof (SqlDbConnection) }, { "s2" , typeof (MySqlDbConnection) } }; return new DbConnectionFactory(connections); }); var sp = services.BuildServiceProvider();var factory = sp.GetRequiredService<DbConnectionFactory>();var s1 = factory.Create(sp, "s1" );var s2 = factory.Create(sp, "s2" );
提供模式
如果看到提供者模式,说明我们可以提供多个方案,支持多实现
一般通过工厂来管理提供者,用以支持命名实列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 public interface ILogger { void Info (string message ) ; } public interface ILoggerProvider { ILogger CreateLogger (string name ) ; } public class ConsoleLoggerProvider : ILoggerProvider { public ILogger CreateLogger (string name ) { return new ConsoleLogger(name); } class ConsoleLogger : ILogger { private string _name; public ConsoleLogger (string name ) { _name = name; } public void Info (string message ) { Console.WriteLine($"{_name} :{message} " ); } } } public class DebugLoggerProvider : ILoggerProvider { public ILogger CreateLogger (string name ) { return new DebugLogger(name); } class DebugLogger : ILogger { private string _name; public DebugLogger (string name ) { _name = name; } public void Info (string message ) { Debug.WriteLine($"{_name} :{message} " ); } } } public class LoggerFactoryBuilder { private List<ILoggerProvider> _providers = new (); public void Add (ILoggerProvider provider ) { _providers.Add(provider); } public LoggerFactory Build () { return new LoggerFactory(_providers); } } public class LoggerFactory { private IEnumerable<ILoggerProvider> _providers = new (); public LoggerFactory (IEnumerable<ILoggerProvider> providers ) { _providers = providers; } public static LoggerFactory Create (Action<LoggerFactoryBuilder> configure ) { var builder = new LoggerFactoryBuilder(); configure(builder); return builder.Build(); } public void AddProvider (ILoggerProvider provider ) { _providers.Add(provider); } public ILogger Create (string name ) { var loggers = _providers.Select(s=>s.CreateLogger(name)); return new LoggerCollection(loggers); } class LoggerCollection : ILogger { private IEnumerable<ILogger> _loggers; public LoggerCollection (IEnumerable<ILogger> loggers ) { _loggers = loggers; } public void Info (string message ) { foreach (var logger in _loggers) { logger.Info(message); } } } }
代理模式
代理模式侧重于对目标对象进行加强,通过实现目标对象的接口具备目标对象的能力。
一般通过实现和目标对象相同的接口来获得目标对象的能力
代理可以通过目标对象来简化实现成本,代理只负责编写加强逻辑
一般代理器只代理单个目标对象,我们把下面这个模式也可以归纳到代理模式,因为它能满足代理的许多特点比如加强、拥有目标对象的能力
思考我们需要一个LoggerCollection,需要实现ICollection接口,如何降低实现成本?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface ILogger { void Info (string message ) ; } public class LoggerCollection : ILogger { private IEnumerable<ILogger> _loggers; public LoggerCollection (IEnumerable<ILogger> loggers ) { _loggers = loggers; } public void Info (string message ) { foreach (var logger in _loggers) { logger.Info(message); } } }
装饰者模式 装饰者模式侧重于添加装饰(方法),装饰者模式在Stream里面使用非常频繁,我们说流本质都是二进制。但是实际操作起来,有的是字符串。于是就有了TextStream、StreamReader把他们装饰成文本流,并提供新的api,我们看一个案例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public interface IOStream { void Write (byte [] buffer ) ; byte [] ReadAll () ; void Close () ; } public class FileStream : IOStream { private List<byte > _buffer = new List<byte >(); public void Write (byte [] buffer ) { _buffer.AddRange(buffer); } public byte [] ReadAll () { return _buffer.ToArray(); } public void Close () { Console.WriteLine("文件已关闭" ); } } public class TextStream : IOStream { private IOStream _stream; public TextStream (IOStream stream ) { _stream = stream; } public void Write (byte [] buffer ) { Console.WriteLine("要开始写入了" ); _stream.Write(buffer); } public byte [] ReadAll () { return _stream.ReadAll(); } public void Close () { Console.WriteLine("释放了" ); } public string ReadToEnd () { return Encoding.UTF8.GetString(ReadAll()); } } IOStream stream1 = new FileStream(); IOStream streamProxy = new TextStream(stream1); TextStream textStream = new TextStream(stream1); var text = textStream.ReadToEnd();
代理类型:一般需要接收一个目标对象(或者内部创建),不关系具体的实现逻辑,交给目标对象实现。代理类多出来的方法我们是不关系的,我们也不会去调用代理类中的函数。只要是实现标准的接口调用了目标对象的方法,我们就认为是代理,否则是重写。
装饰者模式:必须接收一个目标对象,不然装饰谁?装饰器就是为了装饰目标对象的,可以添加额外的方法用于实现装饰,而不是代理。
容器实现 实现容器有三个重要的对象,通过IContainerBuilder来构建Container实列。Container负责根据服务描述来找到服务实现,通过服务实现的依赖来进行注入。下面我们写一个简化版本的容器。
ServiceDescriptor:负责描述服务信息
IContainerBuilder:负责构建容器
IContainer:负责根据服务描述信息解析服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 public class DbConnection { } public class DbContext { public DbConnection Connection { get ; } public DbContext (DbConnection connection ) { Connection = connection; } } public class ServiceDescriptor { public Type ServiceType { get ; } public Type ImplementionType { get ; } public object ? Instance { get ; } public ServiceDescriptor (Type serviceType, Type implementionType, object ? instance = null ) { ServiceType = serviceType; ImplementionType = implementionType; Instance = instance; } } public interface IContainer { object ? GetService(Type serviceType); } public interface IContainerBuilder { void Add (ServiceDescriptor descriptor ) ; IContainer Build () ; } public class Container : IContainer { private IEnumerable<ServiceDescriptor> _services; public Container (IEnumerable<ServiceDescriptor> services ) { _services = services; } public object ? GetService(Type serviceType) { var descriptor = _services .FirstOrDefault(a => a.ServiceType == serviceType); if (descriptor == null ) { throw new InvalidOperationException("服务未注册" ); } var invokerType = typeof (Func<IContainer, object >); if (descriptor.Instance != null && typeof (Func<IContainer, object >).IsInstanceOfType(descriptor.Instance)) { var func = descriptor.Instance as Func<IContainer, object > ?? throw new ArgumentNullException(); return func(this ); } var constructor = serviceType.GetConstructors() .OrderByDescending(a => a.GetParameters().Length) .FirstOrDefault() ?? throw new ArgumentNullException(); var parameters = constructor.GetParameters() .Select(s => GetService(s.ParameterType)); return Activator.CreateInstance(descriptor.ImplementionType, parameters.ToArray()); } } public class ContainerBuilder : IContainerBuilder { private List<ServiceDescriptor> _services = new (); public void Add (ServiceDescriptor descriptor ) { _services.Add(descriptor); } public IContainer Build () { return new Container(_services); } } public static class IContainerBuilderExtensions { public static void Add <TService >(this IContainerBuilder builder ) where TService : class { builder.Add(new ServiceDescriptor(typeof (TService), typeof (TService))); } public static void Add <TService , TImplement >(this IContainerBuilder builder ) { builder.Add(new ServiceDescriptor(typeof (TService), typeof (TImplement))); } public static void Add <TService >(this IContainerBuilder builder, Func<IContainer, TService> func ) { builder.Add(new ServiceDescriptor(typeof (TService), typeof (Action<IContainer, TService>), func)); } } IContainerBuilder builder = new ContainerBuilder(); builder.Add(c => new DbConnection()); builder.Add<DbContext>(); var container = builder.Build();var context = container.GetService(typeof (DbContext));
逆变协变 协变
我们说泛型是不完整的,当指定泛型参数时,才使得其完整。
在接口或者委托上,在泛型参数上使用out关键字,使得完整泛型,完整的泛型满足泛型参数的多态
1 2 3 4 public interface IEnumerable <out T > : IEnumerable { IEnumerator<T> GetEnumerator () ; }
1 2 3 4 5 6 7 IEnumerable<string > list = new List<string >(); IEnumerable<object > obj = list; var flag = typeof (IEnumerable<object >).IsAssignableFrom(typeof (IEnumerable<string >));
1 public delegate TResult Func <out TResult >() ;
1 2 3 4 5 6 Func<string > func1 = () => "ff" ; Func<object > func2 = func1; var flag = typeof (Func<object >).IsAssignableFrom(typeof (Func<string >));
逆变 和协变相反,有点叛逆,目前不知道具体应用场景
Autofac 需要安装Autofac.Extensions.DependencyInjection,是Autofac来集成微软IOC,不是微软去集成它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var services = new ServiceCollection();services.AddScoped(typeof (ILogger<>),typeof (Logger<>)); var builder = new ContainerBuilder();builder.RegisterType<CService>().PropertiesAutowired() .As<CService>() .InstancePerLifetimeScope(); builder.Populate(services); IServiceProvider container = new AutofacServiceProvider(builder.Build()); var logger = container.GetRequiredService<ILogger<Program>>();var service = container.GetRequiredService<CService>();