C#之依赖注入DI(DependencyInjection)
作者:互联网
依赖注入实际上是一种设计模式,它可以有效降低模块之间的耦合度。
基本思路:
-
创建ServiceCollection对象
-
用ServiceCollection对象进行注册服务
-
用ServiceCollection创建ServiceProvider对象,通过ServiceProvider的GetService方法获取服务
而服务分为transient,scoped,singleton三种,其中transient是每次获取都是新的对象,scoped 是只有在范围以内的才是同一个对象,而singleton永远取到的是同一个对象,下面分别进行演示。
transient服务
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服务,每次获取都是新的对象。
singleton服务
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
Scoped
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>
因此,降低了模块之间的耦合度。
标签:string,DI,C#,void,sp,using,services,DependencyInjection,public 来源: https://www.cnblogs.com/johnyang/p/16653953.html