1、前言
作为.NET Core
中最为重要的概念,依赖注入和控制反转可谓是无处不在,因此微软也为我们提供一个内置的IoC
容器,下面就来介绍一下它的使用方法。
2、内置的IoC容器
创建一个控制台程序,引入如下两个组件,版本选择3.1.23
,如下图所示:
1、Microsoft.Extensions.DependencyInjection
2、Microsoft.Extensions.DependencyInjection.Abstractions
还是使用之前的例子,这次我们使用.NET Core
内置的IoC
容器来实现,代码如下所示:
using Microsoft.Extensions.DependencyInjection;
using System;
namespace App
{
class Program
{
static void Main(string[] args)
{
// IServiceCollection负责注册
IServiceCollection services = new ServiceCollection();
services.AddTransient<IDb, SqlServer>();
services.AddTransient<DbManager>();
// IServiceProvider负责提供实例
IServiceProvider provider = services.BuildServiceProvider();
DbManager manager = provider.GetService<DbManager>();
manager.Read();
}
}
public interface IDb
{
void Read();
}
public class SqlServer : IDb
{
public void Read()
{
Console.WriteLine("SqlServer读取数据");
}
}
public class DbManager
{
private IDb db;
public DbManager(IDb db)
{
this.db = db;
}
public void Read()
{
db.Read();
}
}
}
.NET Core
中负责依赖注入和控制反转的核心组件有两个:IServiceCollection
和IServiceProvider
。其中,IServiceCollection
负责注册,IServiceProvider
负责提供实例。与Autofac
类似,.NET Core
内置的IoC
容器也是按照创建容器、注册接口和类、获取对象的流程实现控制反转。
3、容器内实例的生命周期
在注册接口和类时,IServiceCollection
提供了三种注册方法,如下所示:
1、services.AddTransient<IDb, SqlServer>(); // 瞬时生命周期
2、services.AddScoped<IDb, SqlServer>(); // 域生命周期
3、services.AddSingleton<IDb, SqlServer>(); // 全局单例生命周期
3.1、瞬时生命周期——AddTransient
如果使用AddTransient
方法注册,IServiceProvider
每次都会通过GetService
方法创建一个新的实例,代码如下所示:
using Microsoft.Extensions.DependencyInjection;
using System;
namespace App
{
class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IDb, SqlServer>();
IServiceProvider provider = services.BuildServiceProvider();
IDb db_1 = provider.GetService<IDb>();
IDb db_2 = provider.GetService<IDb>();
IDb db_3 = provider.GetService<IDb>();
Console.WriteLine(db_1.GetHashCode());
Console.WriteLine(db_2.GetHashCode());
Console.WriteLine(db_3.GetHashCode());
}
}
public interface IDb
{
void Read();
}
public class SqlServer : IDb
{
public void Read()
{
Console.WriteLine("SqlServer读取数据");
}
}
}
运行结果如下所示:
32854180
27252167
43942917
3.2、域生命周期——AddScoped
如果使用AddScoped
方法注册, 在同一个域(Scope
)内,IServiceProvider
每次都会通过GetService
方法调用同一个实例,可以理解为在局部实现了单例模式,代码如下所示:
using Microsoft.Extensions.DependencyInjection;
using System;
namespace App
{
class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddScoped<IDb, SqlServer>();
IServiceProvider provider = services.BuildServiceProvider();
IDb db_1 = provider.GetService<IDb>();
IDb db_2 = provider.GetService<IDb>();
IDb db_3 = provider.GetService<IDb>();
Console.WriteLine(db_1.GetHashCode());
Console.WriteLine(db_2.GetHashCode());
Console.WriteLine(db_3.GetHashCode());
}
}
public interface IDb
{
void Read();
}
public class SqlServer : IDb
{
public void Read()
{
Console.WriteLine("SqlServer读取数据");
}
}
}
运行结果如下所示:
32854180
32854180
32854180
3.3、全局单例生命周期——AddSingleton
如果使用AddSingleton
方法注册, 在整个应用程序生命周期内,IServiceProvider
只会创建一个实例,代码如下所示:
using Microsoft.Extensions.DependencyInjection;
using System;
namespace App
{
class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IDb, SqlServer>();
IServiceProvider provider = services.BuildServiceProvider();
IDb db_1 = provider.GetService<IDb>();
IDb db_2 = provider.GetService<IDb>();
IDb db_3 = provider.GetService<IDb>();
Console.WriteLine(db_1.GetHashCode());
Console.WriteLine(db_2.GetHashCode());
Console.WriteLine(db_3.GetHashCode());
}
}
public interface IDb
{
void Read();
}
public class SqlServer : IDb
{
public void Read()
{
Console.WriteLine("SqlServer读取数据");
}
}
}
运行结果如下所示:
32854180
32854180
32854180
3.4、AddScoped和AddSingleton的区别
通过上面的代码可以发现,域生命周期和全局单例生命周期的输出结果是一样的,那么它们之间有什么区别?看下面一段代码:
using Microsoft.Extensions.DependencyInjection;
using System;
namespace App
{
class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddScoped<IDb, SqlServer>();
IServiceProvider provider = services.BuildServiceProvider();
using (var scope_1 = provider.CreateScope())
{
var p = scope_1.ServiceProvider;
IDb db_1 = p.GetService<IDb>();
IDb db_2 = p.GetService<IDb>();
IDb db_3 = p.GetService<IDb>();
Console.WriteLine("------------------scope_1------------------");
Console.WriteLine(db_1.GetHashCode());
Console.WriteLine(db_2.GetHashCode());
Console.WriteLine(db_3.GetHashCode());
}
using (var scope_2 = provider.CreateScope())
{
var p = scope_2.ServiceProvider;
IDb db_1 = p.GetService<IDb>();
IDb db_2 = p.GetService<IDb>();
IDb db_3 = p.GetService<IDb>();
Console.WriteLine("------------------scope_2------------------");
Console.WriteLine(db_1.GetHashCode());
Console.WriteLine(db_2.GetHashCode());
Console.WriteLine(db_3.GetHashCode());
}
}
}
public interface IDb
{
void Read();
}
public class SqlServer : IDb
{
public void Read()
{
Console.WriteLine("SqlServer读取数据");
}
}
}
如果采用AddScoped
注册,那么在同一个域中,每个实例的哈希值都是一样的,但在不同域中,它们又是不一样的,其输出结果如下所示:
------------------scope_1------------------
32854180
32854180
32854180
------------------scope_2------------------
27252167
27252167
27252167
如果采用AddSingleton
注册,那么不同域中的实例的哈希值都是一样的,因为AddSingleton
方法注册的实例在全局是唯一的,其输出结果如下所示:
------------------scope_1------------------
32854180
32854180
32854180
------------------scope_2------------------
32854180
32854180
32854180
4、结语
本文主要介绍了.NET Core
中内置的IoC
容器的使用方法。回想一下三层架构的年代,一些公共类,诸如CacheHelper
、CookieHelper
,我们都可以在.NET Core
内置的IoC
容器中把它们注册为全局单例生命周期,而服务层的相关类则可以注册为域生命周期。