依赖注入实际上是一种设计模式,它可以有效降低模块之间的耦合度。
基本思路:
创建ServiceCollection对象
用ServiceCollection对象进行注册服务
用ServiceCollection创建ServiceProvider对象,通过ServiceProvider的GetService方法获取服务
而服务分为transient,scoped,singleton三种,其中transient是每次获取都是新的对象,scoped 是只有在范围以内的才是同一个对象,而singleton永远取到的是同一个对象,下面分别进行演示。
using System; using Microsoft.Extensions.DependencyInjection; namespace DITest { internal class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); services.AddTransient<TestService>(); using(var sp= services.BuildServiceProvider()) { TestService t = sp.GetService<TestService>(); t.Name = "JohnYang"; t.SayHi(); TestService t1 = sp.GetService<TestService>(); Console.WriteLine(Object.ReferenceEquals(t, t1)); } } } public class TestService { public string Name { get; set; } public void SayHi() { Console.WriteLine(Name); } } }
output:
JohnYang False
这确实也验证了transient服务,每次获取都是新的对象。
using System; using Microsoft.Extensions.DependencyInjection; namespace DITest { internal class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //services.AddTransient<TestService>(); services.AddSingleton<TestService>(); //services.AddScoped<TestService>(); using (var sp= services.BuildServiceProvider()) { TestService t = sp.GetService<TestService>(); t.Name = "JohnYang"; t.SayHi(); TestService t1 = sp.GetService<TestService>(); Console.WriteLine(Object.ReferenceEquals(t, t1)); } } } public class TestService { public string Name { get; set; } public void SayHi() { Console.WriteLine(Name); } } }
output:
JohnYang True
using System; using Microsoft.Extensions.DependencyInjection; namespace DITest { internal class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //services.AddTransient<TestService>(); //services.AddSingleton<TestService>(); services.AddScoped<TestService>(); using (var sp= services.BuildServiceProvider()) { TestService t, t1, t2; //指定范围 using(IServiceScope scope = sp.CreateScope()) { //在scope中获取Scope相关的对象,需要用scope.ServiceProvider而不是sp!! t = scope.ServiceProvider.GetService<TestService>(); t.Name = "JohnYang"; t.SayHi(); t1 = scope.ServiceProvider.GetService<TestService>(); Console.WriteLine(Object.ReferenceEquals(t, t1)); } using (IServiceScope scope2 = sp.CreateScope()) { //在scope中获取Scope相关的对象,需要用scope.ServiceProvider而不是sp!! t2 = scope2.ServiceProvider.GetService<TestService>(); Console.WriteLine(Object.ReferenceEquals(t2, t)); } } } } public class TestService { public string Name { get; set; } public void SayHi() { Console.WriteLine(Name); } } }
output:
JohnYang True False
结果也验证了,在同一个范围是同一个服务,但不同范围,获取的不是同一个服务的结论。
不要再长声明周期的对象中引用比它短的生命周期的对象,因为短的生命周期的对象被销毁的时候,长声明周期的对象对它的引用将受影响。
声明周期的选择:如果类无状态(无属性和成员变量),建议为singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一个线程中的,没有并发修改的问题;在使用Transient的时候要谨慎。
接口的形式:
using System; using Microsoft.Extensions.DependencyInjection; namespace DITest { internal class Program { static void Main() { ServiceCollection services = new ServiceCollection(); services.AddScoped<ITestService,TestService>();//第一个是服务的接口,第二个是实现服务的对象 using(var sp = services.BuildServiceProvider()) { ITestService testService = sp.GetService<ITestService>(); testService.Name = "JohnYang"; testService.SayHi(); Console.WriteLine(testService.GetType()); } } } public interface ITestService { public string Name { get; set; } public void SayHi(); } public class TestService:ITestService { public string Name { get; set; } public void SayHi() { Console.WriteLine(Name); } } }
output:
JohnYang DITest.TestService
GetService<T>
中的T必须与AddXXX<T,T1>
中的T是一致的,否则,取不到,返回null,以上面例子来讲,如果GetService<TestService>
就报错,因为注册的是ITestServie
,而不是TestSerive
。
T GetRequiredService<T>()
如果获取不到对象,则抛异常。
IEnumerable<T> GetServices<T>()
适用于可能有很多满足条件的服务。
using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; namespace DITest { internal class Program { static void Main() { ServiceCollection services = new ServiceCollection(); services.AddScoped<ITestService,TestService>();//第一个是服务的接口,第二个是实现服务的对象 using(var sp = services.BuildServiceProvider()) { IEnumerable<ITestService> testServices = sp.GetServices<ITestService>(); foreach(var t in testServices) { Console.WriteLine(t.GetType()); } } } } public interface ITestService { public string Name { get; set; } public void SayHi(); } public class TestService:ITestService { public string Name { get; set; } public void SayHi() { Console.WriteLine(Name); } } }
output:
DITest.TestService
当注册了多个服务的时候,GetServices返回的是所有的实现的对象,而GetServie返回的是最后一个注册的服务。
IEnumerable<object> GetServices(Type serviceType)
。
依赖注入是有“传染性”的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值,但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的类型参数就不会被自动赋值。.NET的DI默认是构造函数注入。
这也是依赖注入非常强大的地方,通过DI创建的对象,该对象构造函数中的参数也会自动的被创建。
Demo如下:
using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; namespace DITest { internal class Program { static void Main() { ServiceCollection services = new ServiceCollection(); //注册各种服务 services.AddScoped<Controller>(); services.AddScoped<ILog, LogImpl>(); services.AddScoped<IStorage, StorageImpl>(); services.AddScoped<IConfig, ConfigImpl>(); using(var sp = services.BuildServiceProvider()) { Controller controller = sp.GetRequiredService<Controller>(); controller.Test(); } Console.ReadKey(); } } 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() { log.Log("开始上传"); storage.Save("asdkks", "1.txt"); log.Log("上传完毕"); } } /// <summary> /// 日志服务 /// </summary> interface ILog { public void Log(string msg); } /// <summary> /// 日志实现类 /// </summary> class LogImpl : ILog { public void Log(string msg) { Console.WriteLine("日志:"+msg); } } /// <summary> /// 配置服务 /// </summary> interface IConfig { public string GetValue(string name); } /// <summary> /// 配置实现类 /// </summary> class ConfigImpl : IConfig { public string GetValue(string name) { return "hello"; } } interface IStorage { public void Save(string content, string name); } class StorageImpl : IStorage { private readonly IConfig _config; public StorageImpl(IConfig config)//构造函数注入,当DI创建StorageImpl时候,框架自动创建IConfig服务 { _config = config; } public void Save(string content, string name) { string server=_config.GetValue("server"); Console.WriteLine($"向服务器{server}的文件名{name}上传{content}"); } } }
output:
日志:开始上传 向服务器hello的文件名1.txt上传asdkks 日志:上传完毕
如果后续,更改配置,则业务代码不用动,只需要
class DbConfigImpl : IConfig { public string GetValue(string name) { return "hello db"; } }
然后,把之前IConfig的服务更改为DbConfigImpl,就可以了。
services.AddScoped<IConfig, DbConfigImpl>
因此,降低了模块之间的耦合度。