文章目录
- 前言
- 什么是依赖注入
- [C# 使用依赖注入](# 使用依赖注入)
- Microsoft.Extensions.DependencyInjection
- 结尾
前言
依赖注入是一个非常重要的编程思想,就和面向过程和面向对象一样,IOC和控制反转是一种解耦的编程思想。
什么是依赖注入
[C#]理解和入门依赖注入
为什么要用IOC:inversion of controll反转控制(把创建对象的权利交给框架)
C# 使用依赖注入
框架介绍
目前.NET 有两个最优的依赖注入框架
- Microsoft.Extensions.DependencyInjection:微软官方依赖注入框架,听说在.net core 8.0得到了最强的性能提升
- Autofac:听说也是最强的依赖注入框架,性能强,开销低,功能完善。
Dependency injection in ASP.NET Core
Autofac 官网
深入浅出依赖注入容器------Autofac
Microsoft.Extensions.DependencyInjection
目前打算用微软的IOC,毕竟是官方背书,性能有保证。
Nuget安装
简单单例使用
声明个测试类
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NETCore8.Models
{
public class Person
{
public int Id { get; set; }
public string ?Name { get; set; }
public int Age { get; set; }
}
}
主函数代码
csharp
using Microsoft.Extensions.DependencyInjection;
using NETCore8.Models;
using Newtonsoft.Json;
using System.ComponentModel.Design;
namespace NETCore8
{
internal class Program
{
static void Main(string[] args)
{
//构造依赖注入容器
IServiceCollection services = new ServiceCollection();
//注入Person单例,生命周期暂时不展开
services.AddSingleton<Person>();
var builder = services.BuildServiceProvider();
//初始化单例
var res = builder.GetService<Person>();
res.Name = "小刘";
res.Age = 15;
Console.WriteLine(JsonConvert.SerializeObject(res));
//从容器中拿到Person单例,确认是否已被赋值为小刘
var res2 = builder.GetService<Person>();
Console.WriteLine(JsonConvert.SerializeObject(res2));
//修改单例,查看容器中的单例是否被修改
res2.Name = "小红";
res2.Age = 23;
//再从容器中拿出单例
var res3 = builder.GetService<Person>();
Console.WriteLine(JsonConvert.SerializeObject(res3));
Console.WriteLine("Hello, World!");
Console.ReadKey();
}
}
}
打印结果
这个说明这个单例一旦被修改了,容器中的数据就会被修改。但是这样仅仅是和全局静态的效果一样。依赖注入没有这么简单
自动装配
自动装配的意思就是自动依赖注入。就是你不需要主动去声明构造函数,IOC容器会自动帮你去使用构造函数。
举例
这里为了简单说明,这里只使用单例自动装配举例。
csharp
namespace IOC_Test.Models
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
csharp
namespace IOC_Test.Services
{
public class PersonService
{
public Person Person { get; set; }
/// <summary>
/// 无参构造函数
/// </summary>
public PersonService() {
Person = new Person();
}
/// <summary>
/// 有参构造函数,IOC是选择尽可能多的参数构造
/// </summary>
/// <param name="person"></param>
public PersonService(Person person)
{
this.Person = person;
}
}
}
自动装配测试用例
csharp
using IOC_Test.Models;
using IOC_Test.Services;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
namespace IOC_Test
{
internal class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
//注入依赖
services.AddSingleton<Person>();
services.AddSingleton<PersonService>();
//生成IOC容器
var builder = services.BuildServiceProvider();
//两次打印,第一次打印PersonService的Person
{
var res = builder.GetService<PersonService>();
Console.WriteLine(JsonConvert.SerializeObject(res?.Person));
}
//修改Person,看看PersonService里面是不是会受影响
{
var res = builder.GetService<Person>();
res.Name = "小王";
res.Age = 10;
}
//再次打印,如果被修改,那么就说明是自动装配。如果没被修改,就说明没有将Person自动注入到PersonService
{
var res = builder.GetService<PersonService>();
Console.WriteLine(JsonConvert.SerializeObject(res?.Person));
}
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
}
}
打印结果
自动装配执行顺序
测试用例
这里我们新建一个Dog
csharp
namespace IOC_Test.Models
{
public class Dog
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
Person
csharp
namespace IOC_Test.Models
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
PersonService
csharp
namespace IOC_Test.Services
{
public class PersonService
{
public Person Person { get; set; }
public Dog Dog { get; set; }
/// <summary>
/// 无参构造函数
/// </summary>
public PersonService() {
Person = new Person();
}
}
}
主函数
csharp
namespace IOC_Test
{
internal class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
//注入依赖
services.AddSingleton<Person>();
services.AddSingleton<PersonService>();
services.AddSingleton<Dog>();
//生成IOC容器
var builder = services.BuildServiceProvider();
//两次打印,第一次打印PersonService
{
var res = builder.GetService<PersonService>();
Console.WriteLine(JsonConvert.SerializeObject(res));
}
//修改Person和Dog,看看PersonService里面是不是会受影响
{
var person = builder.GetService<Person>();
person.Name = "小王";
person.Age = 10;
var dog = builder.GetService<Dog>();
dog.Name = "旺财";
dog.Age = 2;
}
//再次打印,查看自动装配如何执行
{
var res = builder.GetService<PersonService>();
Console.WriteLine(JsonConvert.SerializeObject(res));
}
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
}
}
有歧义构造函数
csharp
namespace IOC_Test.Services
{
public class PersonService
{
public Person Person { get; set; }
public Dog Dog { get; set; }
/// <summary>
/// 无参构造函数
/// </summary>
public PersonService() {
Person = new Person();
}
public PersonService(Person person)
{
this.Person = person;
}
public PersonService(Dog dog) {
this.Dog = dog;
}
}
}
如果构造函数出现歧义,比如这里既可以选择Person构造,又可以选择Dog构造,会报错
渐进式构造函数
csharp
namespace IOC_Test.Services
{
public class PersonService
{
public Person Person { get; set; }
public Dog Dog { get; set; }
/// <summary>
/// 无参构造函数
/// </summary>
public PersonService() {
Person = new Person();
}
public PersonService(Person person)
{
this.Person = person;
}
public PersonService(Person person,Dog dog) {
this.Person= person;
this.Dog = dog;
}
}
}
运行成功
循环依赖
Person注入Dog,Dog注入Person,看看效果如何
csharp
namespace IOC_Test.Models
{
public class Person
{
public Dog Dog { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Person(Dog dog)
{
Dog = dog;
}
}
}
csharp
namespace IOC_Test.Models
{
public class Dog
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Person Person { get; set; }
public Dog(Person person)
{
Person = person;
}
}
}
自动装配结论
自动装配是尽可能主动去装配服务,如果出现装配歧义,循环依赖,那么就会主动抛出异常。自动装配可以极大的减少对构造函数维护,我们不需要知道服务是怎么声明的,IOC容器会帮助我们自动声明相互之间的依赖。这张图就能很好的解释自动装配的效果
手动装配
自动装配是由IOC容器自动装配的类。如果需要装配多个同类的服务,那就要手动进行区别了。
手动注入
csharp
internal class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddSingleton<Person>(sp =>
{
var res = new Person() {
Name = "小红",
Age = 19
};
return res;
});
//生成容器
var builder = services.BuildServiceProvider();
{
var res = builder.GetService<Person>();
Console.WriteLine(JsonConvert.SerializeObject(res));
}
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
}
别名注入
csharp
namespace IOC_Test
{
internal class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddKeyedSingleton<Person>("A",(sp,key) =>
{
var res = new Person() {
Name = "小红",
Age = 19
};
return res;
});
services.AddKeyedSingleton<Person>("B", (sp, key) =>
{
var res = new Person()
{
Name = "小蓝",
Age = 23
};
return res;
});
//生成容器
var builder = services.BuildServiceProvider();
//获取服务,当Key找不到时自动返回Null
{
var res = builder.GetService<Person>();
Console.WriteLine("获取默认服务");
Console.WriteLine(JsonConvert.SerializeObject(res));
}
{
var res = builder.GetKeyedService<Person>("A");
Console.WriteLine("获取A,Person");
Console.WriteLine(JsonConvert.SerializeObject(res));
}
{
var res = builder.GetKeyedService<Person>("B");
Console.WriteLine("获取B,Person");
Console.WriteLine(JsonConvert.SerializeObject(res));
}
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
}
}
声明别名的服务将不会自动装配,即使声明的别名相同。建议使用多个不同名的服务来自动装配。手动声明别名需要手动装配对应关系
也可以在输入的时候主动拿到按照Key去寻找服务。
csharp
internal class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
//依赖注入是使用的时候去构造,所以声明顺序不影响实际运行顺序,有点类似于回调函数
services.AddKeyedSingleton<Person>("A",(sp,key) =>
{
//Console.WriteLine(key);
var res = new Person() {
Name = "小红",
Age = 19
};
return res;
});
services.AddKeyedSingleton<PersonService>("A", (sp, key) =>
{
return new PersonService(sp.GetKeyedService<Person>(key));
});
//生成容器
var builder = services.BuildServiceProvider();
//获取服务
{
var res = builder.GetKeyedService<Person>("A");
Console.WriteLine("获取默认服务");
Console.WriteLine(JsonConvert.SerializeObject(res));
}
//获取服务
{
var res = builder.GetKeyedService<PersonService>("A");
Console.WriteLine("获取默认服务");
Console.WriteLine(JsonConvert.SerializeObject(res));
}
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
}
依赖注入的构造顺序
依赖注入是使用的时候去生成,而不是注入的时候生成
csharp
namespace IOC_Test
{
internal class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddKeyedSingleton<Person>("A",(sp,key) =>
{
Console.WriteLine($"构造函数执行,key[{key}]");
var res = new Person() {
Name = "小红",
Age = 19
};
return res;
});
//生成容器
var builder = services.BuildServiceProvider();
//获取服务
{
Console.WriteLine("获取Key[A]服务");
var res = builder.GetKeyedService<Person>("A");
Console.WriteLine(JsonConvert.SerializeObject(res));
}
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
}
}
结尾
IOC容器还有许多别的功能,比如别名,接口注入,注解注入,声明周期等。这个我还不太了解。现在的单例自动装配已经基本满足了我的功能,我以后有时间会去深入了解。