前言
什么是依赖注入?
依赖注入(Dependency Injection),简称DI。
由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。
依赖关系反转
应用程序中的依赖关系方向应该是抽象的方向,而不是实现详细信息的方向。 大部分应用程序都是这样编写的:编译时依赖关系顺着运行时执行的方向流动,从而生成一个直接依赖项关系图。 也就是说,如果类 A 调用类 B 的方法,类 B 调用 C 类的方法,则在编译时,类 A 将取决于类 B,而 B 类又取决于类 C,如图所示
直接关系依赖
应用依赖关系反转原则后,A 可以调用 B 实现的抽象上的方法,让 A 可以在运行时调用 B,而 B 又在编译时依赖于 A 控制的接口(因此,典型的编译时依赖项发生反转)。 运行时,程序执行的流程保持不变,但接口引入意味着可以轻松插入这些接口的不同实现。
反转依赖项关系图
依赖项反转是生成松散耦合应用程序的关键一环,因为可以将实现详细信息编写为依赖并实现更高级别的抽象,而不是相反。 因此,生成的应用程序的可测试性、模块化程度以及可维护性更高。 遵循依赖关系反转原则可实现依赖关系注入。
NET 中的依赖关系注入
.NET 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。 .NET 中的依赖关系注入是框架的内置部分。
内置的IOC容器
通过nuget添加引用
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.DependencyInjection.Abstractions
internal class Program
{
private static void Main(string[] args)
{
DbHelper dbHelper = new DbHelper();
dbHelper.DoConnect();
}
}
public class DbHelper
{
private readonly Redis redis = new Redis();
public void DoConnect()
{
redis.Connect();
}
}
public class Redis
{
public void Connect()
{
Console.WriteLine($"HashCode:{this.GetHashCode()}Redis.....Connect");
}
}
在上面代码中,类DbHelper
创建并直接依赖于Redis
类。 硬编码的依赖项会产生问题,应避免使用,原因如下:
- 要用不同的实现替换
Redis
,必须修改DbHelper
类。- 如果 Redis 具有依赖项,则必须由
DbHelper
类对其进行配置。 在具有多个依赖于Redis
的类的大型项目中,配置代码将分散在整个应用中。- 很难进行单元测试。
通过依赖关系注入方式可以解决以上问题
- 使用接口或基类将依赖关系实现抽象化。
- 在服务容器中注册依赖关系。
public interface IDbHelper
{
void Connect();
}
public class Redis : IDbHelper
{
public void Connect()
{
Console.WriteLine($"HashCode:{this.GetHashCode()}Redis.....Connect");
}
}
IDbHelper
接口定义Connect
方法并由Redis
实现.NET Core中通过IServiceCollection和IServiceProvider两个组件实现依赖注入和控制反转。
IServiceCollection
-注册IServiceProvider
-提供实例。
using Microsoft.Extensions.DependencyInjection;
internal class Program
{
private static void Main(string[] args)
{
IServiceCollection service = new ServiceCollection();
service.AddTransient<IDbHelper, Redis>();
IServiceProvider serviceProvider = service.BuildServiceProvider();
var instance = serviceProvider.GetService<IDbHelper>();
DbHelper dbHelper = new DbHelper(instance);
dbHelper.DoConnect();
}
}
生命周期
Transient
Scoped
Singleton
下列各部分描述了上述每个生存期。 为每个注册的服务选择适当的生存期。
暂时---
AddTransient
暂时生存期服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。 在处理请求的应用中,在请求结束时会释放暂时服务。
范围内---
AddScoped
对于 Web 应用,指定了作用域的生存期指明了每个客户端请求(连接)创建一次服务。在处理请求的应用中,在请求结束时会释放有作用域的服务。
单例---
AddSingleton
创建单例生命周期服务的情况如下:在首次请求它们时进行创建或者在向容器直接提供实现实例时由开发人员进行创建。来自依赖关系注入容器的服务实现的每一个后续请求都使用同一个实例。 如果应用需要单一实例行为,则允许服务容器管理服务的生存期。
namespace IOC.Common
{
public interface IDbHelper
{
void Connect();
}
public interface ITransient : IDbHelper
{
}
public interface ISingleton : IDbHelper
{
}
public interface IScoped : IDbHelper
{
}
public class SqlServer : ITransient
{
public void Connect()
{
Console.WriteLine($"HashCode:{this.GetHashCode()}SqlServer.....Connect");
}
}
public class MySql : IScoped
{
public void Connect()
{
Console.WriteLine($"HashCode:{this.GetHashCode()}MySql.....Connect");
}
}
public class Oracle : ISingleton
{
public void Connect()
{
Console.WriteLine($"HashCode:{this.GetHashCode()}Oracle.....Connect");
}
}
}
下述代码分别使用了
AddTransient
AddScoped
AddSingleton
注册了不同的服务,通过输出结果可以看出三种生命周期的区别
// See https://aka.ms/new-console-template for more information
using IOC.Common;
using Microsoft.Extensions.DependencyInjection;
internal class Program
{
private static void Main(string[] args)
{
var services = new ServiceCollection();
//register service
services.AddTransient<ITransient, SqlServer>();
services.AddSingleton<ISingleton, Oracle>();
services.AddScoped<IScoped, MySql>();
var provider = services.BuildServiceProvider();
Console.WriteLine($"**********************************LiftTime:Transient**********************************");
TestTransient(provider);
Console.WriteLine($"**********************************LiftTime:Singleton**********************************");
TestSingleton(provider);
Console.WriteLine($"**********************************LiftTime:Scoped**********************************");
TestScoped(provider);
Console.ReadLine();
}
private static void TestScoped(ServiceProvider provider)
{
for (int i = 0; i < 5; i++)
{
using (var scope = provider.CreateScope())
{
Console.WriteLine($"**********************************Scope:{scope.GetHashCode()}**********************************");
IDbHelper db = scope.ServiceProvider.GetService<IScoped>();
if (db != null)
{
db.Connect();
}
}
}
}
private static void TestSingleton(ServiceProvider provider)
{
for (int i = 0; i < 5; i++)
{
IDbHelper db = provider.GetService<ISingleton>();
if (db != null)
{
db.Connect();
}
}
}
public static void TestTransient(ServiceProvider provider)
{
for (int i = 0; i < 5; i++)
{
IDbHelper db = provider.GetService<ITransient>();
if (db != null)
{
db.Connect();
}
}
}
}
写在最后
本文主要介绍了.NET Core中内置的IoC容器的使用方法
在Microsoft.Extensions.DependencyInjection中只能用构造函数注入