依赖注入官方文档:https://learn.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection 依赖注入的原理是——反射 如果想用依赖注入框架需要: 先用NuGet下载Microsoft.Extensions.DependencyInjection。 使用名称空间using Microsoft.Extensions.DependencyInjection;
依赖注入框架的简单使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 internal class Program { static void Main(string[] args) { var sc = new ServiceCollection();//创建容器 sc.AddScoped(typeof(IStudent),typeof(Student));//接口描述,绑定该接口的类型的描述 var sp = sc.BuildServiceProvider(); // ==============================以下不再有new操作符 IStudent student = sp.GetService<IStudent>(); student.Study(); } } interface IStudent { void Study(); } class Student : IStudent { public void Study() { Console.WriteLine("我要学习"); } }
依赖注入的几个概念 服务:就是你需要的对象; 注册服务:对象不能凭空出现,需要先注册; 服务容器:负责管理注册的服务 查询服务:创建对象及关联对象; 对象生命周期:Transient(瞬态)、Scoped(范围内单例)、Singleton(单例); 可以指定服务类型和实现类型,或者服务类型和实现类型是同一类型 服务类型尽量用接口 微软的控制反转组件(DependencyInjection)包含服务定位器和依赖反转
服务定位器的写法 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 using Microsoft.Extensions.DependencyInjection; namespace Test { internal class Program { static void Main(string[] args) { //1、创建容器 ServiceCollection services = new ServiceCollection(); //2、注册服务 services.AddTransient<TestServiceImp1>(); //3、创建服务提供者(相当于服务定位器) using (var sp = services.BuildServiceProvider()) { //4、从提供者获取服务 TestServiceImp1 t1 = sp.GetService<TestServiceImp1>(); t1.Name = "Test1"; t1.SayHi(); } } } public interface ITestService { public string Name { get; set; } public void SayHi(); } public class TestServiceImp1 : ITestService { public string Name { get; set; } public void SayHi() { Console.WriteLine("hi"); } } public class TestServiceImp2 : ITestService { public string Name { get; set; } public void SayHi() { Console.WriteLine("你好"); } } }
瞬时模式与单例模式Transient、Singleton 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //Transient //services.AddTransient<TestServiceImp1>(); //Singleton //services.AddSingleton<TestServiceImp1>(); using (var sp = services.BuildServiceProvider()) { TestServiceImp1 t1 = sp.GetService<TestServiceImp1>(); t1.Name = "Test1"; TestServiceImp1 t2 = sp.GetService<TestServiceImp1>(); t2.Name = "Test2"; Console.WriteLine(object.ReferenceEquals(t1, t2));//判断是否为同一对象 Console.WriteLine(t1.Name); } }
范围模式Scope Scope 如果一个类实现了IDisposable接口,则离开作用域之后,容器会自动调用对象的Dispose方法 不要在长生命周期的对象中引用比它短的声明周期的对象 生命周期的选择: 如果类无状态(无属性,无成员变量),建议为Singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一线程中,没有并发修改的问题;使用Transient要谨慎
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 static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //Scoped services.AddScoped<TestServiceImp1>(); //老大sp using (var sp = services.BuildServiceProvider()) { //老大创造了小弟scope1 //创建范围,这个范围就是using的代码块 using (IServiceScope scope1 = sp.CreateScope()) { //在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp //想获取服务不能直接问老大要,应该问小弟scope1要 TestServiceImp1 t1 = scope1.ServiceProvider.GetService<TestServiceImp1>(); TestServiceImp1 t2 = scope1.ServiceProvider.GetService<TestServiceImp1>(); Console.WriteLine(object.ReferenceEquals(t1, t2)); } //老大创造小弟scope2 //创建范围,这个范围就是using的代码块 using (IServiceScope scope2 = sp.CreateScope()) { //在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp //想获取服务不能直接问老大要,应该问小弟scope2要 TestServiceImp1 t1 = scope2.ServiceProvider.GetService<TestServiceImp1>(); TestServiceImp1 t2 = scope2.ServiceProvider.GetService<TestServiceImp1>(); Console.WriteLine(object.ReferenceEquals(t1, t2)); } } }
扩充 当服务类型与实现类型不一致时:
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 static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //泛型方法 services.AddScoped<ITestService, TestServiceImp1>(); services.AddScoped<ITestService, TestServiceImp2>(); //非泛型方法 //services.AddScoped(typeof(ITestService),typeof(TestServiceImp1)); //自定义单例对象 //services.AddSingleton(typeof(ITestService),new TestServiceImp1()); using (var sp = services.BuildServiceProvider()) { //获取服务时,<>里面是注册类型而不是实现类型 //如果找不到服务就返回null ITestService ts1 = sp.GetService<ITestService>(); //如果找不到直接抛异常而不是返回null //ITestService ts1 = sp.GetRequiredService<ITestService>(); //非泛型方法,返回object,使用时需要手动转换 //ITestService ts1 = (ITestService)sp.GetService(typeof(ITestService)); //一个注册类型对应多个实现类型时,可以获取全部 //如果不用GetServices而用GetService就以最后一个添加进来的为准 IEnumerable<ITestService> tests = sp.GetServices<ITestService>(); foreach (ITestService test in tests) { Console.WriteLine(test.GetType()); } ts1.Name = "Test1"; ts1.SayHi(); Console.WriteLine(ts1.GetType()); } }
依赖注入写法 依赖注入是有传染性的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但如果一个对象是程序员手动创建的,那么这个类就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。 .Net的DI默认是构造函数注入 你只管声明,其余交给DI 降低模块之间的耦合
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 using Microsoft.Extensions.DependencyInjection; namespace Test { internal class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //日志服务或者配置服务需要替换只需要在这里更改就行了 services.AddScoped<Controller>(); services.AddScoped<ILog, LogImp1>(); services.AddScoped<IStorage, StorageImp1>(); //如果从本地读取配置 //services.AddScoped<IConfig, ConfigImp1>(); //如果从数据库读取配置 services.AddScoped<IConfig, DbConfig>(); using(var sp = services.BuildServiceProvider()) { var c = sp.GetRequiredService<Controller>(); c.Test(); } } } class Controller { private readonly ILog log; private readonly IStorage storage; public Controller(ILog log, IStorage storage) { this.log = log; this.storage = storage; } public void Test() { this.log.Log("开始上传"); this.storage.Save("asdfa", "1.txt"); this.log.Log("上传完毕"); } } interface ILog { public void Log(string msg); } class LogImp1 : ILog { public void Log(string msg) { Console.WriteLine($"日志:{msg}"); } } interface IConfig { public string GetValue(string name); } class ConfigImp1 : IConfig { public string GetValue(string name) { return "SqlServer"; } } interface IStorage { public void Save(string content,string name); } //如果从本地配置文件读取配置 class StorageImp1 : IStorage { private readonly IConfig config; public StorageImp1(IConfig config) { this.config = config; } public void Save(string content, string name) { string server = config.GetValue("server"); Console.WriteLine($"向服务器{server}的文件名为{name}上传{content}"); } } //如果从数据库读取配置 class DbConfig : IConfig { public string GetValue(string name) { return "MySql"; } } }