目录
- 前言:
- 一、使用容器注入和创建对象
- 二、基类或接口有多次注册
- 三、根容器和子容器创建实例
- 四、容器创建的实例释放的策略
- 五、多个构造函数时容器的选择策略
- 参考文档
前言:
ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术。
ASP.NET Core 内置的依赖注入框架需要引入NuGet包Microsoft.Extensions.DependencyInjection
,
该包依赖于Microsoft.Extensions.DependencyInjection.Abstractions
。
后者是抽象,前者是具体实现,这就是 接口实现分离模式
。
一、使用容器注入和创建对象
首先准备三个类
public interface ILogger { }
public class Logger : ILogger { }
public interface IServer { }
public class Server : IServer { }
public interface IHttp { }
public class Http : IHttp { }
将三个类注册到不同的生命周期中,然后循环打印注册的类型信息。
最后 Build 出 容器(服务提供对象),使用 容器 创建对象,并断言对象是否为预期的对象。
var collection = new ServiceCollection()
.AddTransient<ILogger, Logger>()
.AddScoped<IServer, Server>()
.AddSingleton<IHttp, Http>();
foreach (var item in collection)
{
var log = $"Service: {item.ServiceType.FullName} Lifetime: {item.Lifetime}\nInstance: {item.ImplementationType?.FullName}\n";
Console.WriteLine(log);
}
var provider = collection.BuildServiceProvider();
Debug.Assert(provider.GetService<ILogger>() is Logger);
Debug.Assert(provider.GetService<IServer>() is Server);
Debug.Assert(provider.GetService<IHttp>() is Http);
二、基类或接口有多次注册
首先准备三个类并继承同一个基类
public class Base { }
public interface ILogger { }
public class Logger : Base, ILogger { }
public interface IServer { }
public class Server : Base, IServer { }
public interface IHttp { }
public class Http : Base, IHttp { }
将三个类注册到容器之后,Build 出 容器(服务提供对象)。
使用 容器 创建对象,并断言重复注册的基类得到的对象是最后一个。
使用 容器 的 GetServices 方法可以获取所有注册类型的对象,并按照注册顺序排列。
var collection = new ServiceCollection()
.AddTransient<Base, Logger>()
.AddScoped<Base, Server>()
.AddScoped<Base, Http>();
var provider = collection.BuildServiceProvider();
Debug.Assert(provider.GetService<Base>() is Http);
var bases = provider.GetServices<Base>().ToList();
Debug.Assert(bases[0] is Logger);
Debug.Assert(bases[1] is Server);
Debug.Assert(bases[2] is Http);
三、根容器和子容器创建实例
ASP.NET Core 内置的依赖注入框架 有个根容器和子容器的概念,
子容器只关心根容器,子容器和子容器创建的子容器是平级的。
单例模式 Singleton 保存在根容器
作用域模式 Scoped 保存在当前容器
瞬时模式 Transient 不保存,一次性的
首先准备三个类并继承同一个基类,基类构造函数中打印对象信息。
public class Base
{
public Base() => Console.WriteLine($"Created:{GetType().Name}");
}
public interface ILogger { }
public class Logger : Base, ILogger { }
public interface IServer { }
public class Server : Base, IServer { }
public interface IHttp { }
public class Http : Base, IHttp { }
通过 ServiceCollection 对象 Build 的 容器 就是根容器,即这里的 root,
在这里创建根容器以后,再通过根容器的 CreateScope 方法创建两个服务范围(作用域),
通过访问服务范围的ServiceProvider属性,可以拿到服务范围对应的子容器。
最后我们通过不同的子容器连续两次创建不同的生命周期的实例。
var root = new ServiceCollection()
.AddTransient<ILogger, Logger>()
.AddScoped<IServer, Server>()
.AddSingleton<IHttp, Http>()
.BuildServiceProvider();
var child1 = root.CreateScope().ServiceProvider;
var child2 = root.CreateScope().ServiceProvider;
child1.GetService<ILogger>();
child1.GetService<ILogger>();
child1.GetService<IServer>();
child1.GetService<IServer>();
child1.GetService<IHttp>();
child1.GetService<IHttp>();
Console.WriteLine();
child2.GetService<ILogger>();
child2.GetService<ILogger>();
child2.GetService<IServer>();
child2.GetService<IServer>();
child2.GetService<IHttp>();
child2.GetService<IHttp>();
上面代码运行得到的结果为如下,
根据结果我们可以得知,
生命周期注册为 Transient 时,每次通过容器创建对象时都是全新的对象。
生命周期注册为 Scoped 时,同一个子容器中只会创建一次。
生命周期注册为 Singleton 时,不同的子容器只能创建一次并复用。
> Created:Logger
> Created:Logger
> Created:Server
> Created:Http
> Created:Logger
> Created:Logger
> Created:Server
四、容器创建的实例释放的策略
首先准备三个类并继承同一个基类,且基类实现 IDisposable
public class Base : IDisposable
{
public Base() => Console.WriteLine($"Created:{GetType().Name}");
public void Dispose() => Console.WriteLine($"Disposed: {GetType().Name}");
}
public interface ILogger { }
public class Logger : Base, ILogger { }
public interface IServer { }
public class Server : Base, IServer { }
public interface IHttp { }
public class Http : Base, IHttp { }
通过 using 释放容器,打印日志查看对象释放点
var collection = new ServiceCollection()
.AddTransient<ILogger, Logger>()
.AddScoped<IServer, Server>()
.AddSingleton<IHttp, Http>();
using (ServiceProvider root = collection.BuildServiceProvider())
{
using (IServiceScope scope = root.CreateScope())
{
var child = scope.ServiceProvider;
var logger = child.GetService<ILogger>();
var server = child.GetService<IServer>();
var http = child.GetService<IHttp>();
Console.WriteLine("子容器释放");
}
Console.WriteLine("根容器释放");
}
上面代码运行得到的结果为如下,
可以知道在容器释放对应生命周期的对象也会释放。
> Created:Logger
> Created:Server
> Created:Http
> 子容器释放
> Disposed: Server
> Disposed: Logger
> 根容器释放
> Disposed: Http
五、多个构造函数时容器的选择策略
首先准备四个类,其中 User 不会注册到容器中,Server 类中有三个构造函数。
public interface ILogger { }
public class Logger : ILogger { }
public interface IHttp { }
public class Http : IHttp { }
public interface IUser { }
public class User : IUser { }
public interface IServer { }
public class Server : IServer
{
public Server(ILogger logger)
=> Console.WriteLine($"Ctor(ILogger)");
public Server(ILogger logger, IHttp http)
=> Console.WriteLine($"Ctor(ILogger, IHttp)");
public Server(ILogger logger, IHttp http, IUser user)
=> Console.WriteLine($"Ctor(ILogger, IHttp, IUser)");
}
注册 Logger,Http,Server,不注册 User。
Build 出容器以后,使用容器创建类型为 Server 的对象。
var root = new ServiceCollection()
.AddScoped<ILogger, Logger>()
.AddScoped<IHttp, Http>()
.AddScoped<IServer, Server>()
.BuildServiceProvider();
root.GetService<IServer>();
上面的代码得到的结果为如下,
可以得到结论,
容器会提供构造函数所需要的所有参数,如果某个构造函数的参数类型集合,是所有合法构造函数参数类型集合的超集,那么这个构造函数就会被容器选择。
如果不存在某个构造函数的参数类型集合是所有合法构造函数参数类型集合的超集,并且有至少两个构造函数参数类型集合之间互存在差集,那么程序将会出现异常。
> Ctor(ILogger, IHttp)
参考文档
dotnet / runtime - Microsoft.Extensions.DependencyInjection