.NET 依赖注入DI 注册服务
一、概念
控制反转
IoC/DI容器控制应用主程序。
IoC/DI容器控制对象本身的创建、实例化;控制对象之间的依赖关系。
3、何谓反转(对应于正向)?
因为现在应用程序不能主动去创建对象了,而是被动等待对象容器给它注入它所需要的资源,所以称之为反转。
4、哪些方面反转了?
1.创建对象
2.程序获取资源的方式反了
5、为何需要反转?
1.引入IoC/DI容器过后,体系更为松散,而且管理和维护以及项目升级更有序;
2.类之间真正实现了解耦
依赖
1、什么是依赖(按名称理解、按动词理解)?
依赖(按名称理解):依赖关系;
依赖(按动词理解):依赖的动作
2、谁依赖于谁?
应用程序依赖于IoC/DI容器
3、为什么需要依赖?
因为发生了反转,应用程序依赖的资源都是IoC/DI容器里面
4、依赖什么东西?
应用程序依赖于IoC/DI容器为它注入所需要的资源。(比如:依赖关系)
注入
1、谁注入于谁?
IoC/DI容器注入于应用程序。
2、注入什么东西?
注入应用程序需要的对象,比如依赖关系。
3、为何要注入?
因为程序要正常运行需要访问这些对象
IOC(控制反转Inversion of Control)
控制反转(Inversion of Control)就是使用对象容器反过来控制应用程序所需要的外部资源,这样的一种程序开发思想,调用者不再创建被调用者的实例,由IOC框架实现(容器创建)所以称为控制反转;
创建对象和对象非托管资源的释放都由外部容器去完成,实现项目层与层之间的解耦的一种设计思想。
DI(依赖注入)和DIP(依赖倒置原则)
相信很多人还分不清楚DI和DIP这两个词,甚至认为它们就是同一个词。
1、依赖倒置原则(Dependency Inversion Principle)为我们提供了降低模块间耦合度的一种思路,而依赖注入(Dependency Injection)是一种具体的实施方法,
容器创建好实例后再注入调用者称为依赖注入,就是应用程序依赖IOC容器来注入所需要的外部资源,这样一种程序的开发思想。
2、能做什么(What)?
松散耦合对象,解耦项目架构层。
3、怎么做(How)?
使用Autofac/Unity/Spring等框架类库,里面有实现好了的IoC/DI容器。
4、用在什么地方(Where)?
凡是程序里面需要使用外部资源的情况,比如创建对象,都可以考虑使用IoC/DI容器。
DI和IOC是同一概念吗?
肯定不是同一概念啊,但它们两个描述的是同一件事件,从不同的角度来说:IOC是从对象容器的角度;DI是从应用程序的角度。
1、控制反转的描述:对象容器反过来控制应用程序,控制应用程序锁所需要的一些对象,比如DbContext。
2、依赖注入的描述:应用程序依赖对象容器,依赖它注入所需要的外部资源。
对IoC的理解:
1、应用程序无需主动new对象,而是描述对象应该如何被创建(构造方法、属性、方法参数等)。
2、应用程序不需要主动装配对象之间的依赖关系,而是描述需要哪个服务,IoC容器会帮你装配,被动接受装配。
3、主动变被动,是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。
白话解释:
为啥使用依赖注入
private ILoginService<ApplicationUser> _loginService;
public AccountController()
{
//new
_loginService = new EFLoginService()
}
public AccountController(ILoginService<ApplicationUser> loginService)
{
_loginService = loginService;
}
对象生命周期: IServiceScope
- AddTransient(瞬态):每次调用都创建一个新的对象,即使同一个请求获取多次也会是不同的实例
- AddScoped(范围); 一个范围(using)内拿到同一个对象 类有状态且有Scope控制,每次请求,都获取一个新的实例。同一个请求获取多次会得到相同的实例
- AddSingleton(单例) : 同一个对象 类无状态(没有属性,没有成员变量) 第一个请求上创建一个新实例,并且在应用程序的剩余生命周期中,将相同的实例提供给所有使用者类。
- 范围服务通常应由单个Web请求/线程使用。因此,不应该在线程之间共享服务范围。
- 配置为单例的服务可能会导致应用程序中的内存泄漏。
- 内存泄漏通常是由单例服务引起的。这是因为创建的实例不会被丢弃,它将保留在内存中直到应用程序结束。因此,一旦不使用它们,最好将它们释放。
- 将服务注册为临时服务会缩短其使用寿命,通常可能不太在乎多线程和内存泄漏。
- 不要在单例服务中依赖瞬态或作用域服务。因为瞬时服务在单例服务注入时成为一个单例实例,并且如果瞬态服务不旨在支持这种情况,则可能导致问题。在这种情况下,ASP.NET Core的默认DI容器已经引发异常。
二、使用DI
//注册
ServiceCollection services = new ServiceCollection();
//services.AddTransient<TestServiceImp1>();//瞬态
//services.AddSingleton<TestServiceImp1>();//单例
services.AddScoped<TestServiceImp1>();//范围
//建立 ServiceProvider 服务定位器
using (ServiceProvider sp = services.BuildServiceProvider())
{
////使用 要个一个对象
//TestServiceImp1 t = sp.GetService<TestServiceImp1>();
//t.Name = "lily";
//t.say();
//TestServiceImp1 t1 = sp.GetService<TestServiceImp1>();
//Console.WriteLine(object.ReferenceEquals(t, t1));
//t1.Name = "tom";
//t1.say();
//t.say();//1、瞬态 lily 新的对象 2、单例 tom 同一对象
TestServiceImp1 tt1;
//using 定义了Scoped 范围
using (IServiceScope scope1 = sp.CreateScope())
{
//在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp
TestServiceImp1 t = scope1.ServiceProvider.GetService<TestServiceImp1>();
t.Name = "lily";
t.say();
TestServiceImp1 t1 = scope1.ServiceProvider.GetService<TestServiceImp1>();
Console.WriteLine(object.ReferenceEquals(t, t1));//ture
tt1 = t;
}
using (IServiceScope scope2 = sp.CreateScope())
{
//在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp
TestServiceImp1 t = scope2.ServiceProvider.GetService<TestServiceImp1>();
t.Name = "lily";
t.say();
TestServiceImp1 t1 = scope2.ServiceProvider.GetService<TestServiceImp1>();
Console.WriteLine(object.ReferenceEquals(t, t1));//ture
Console.WriteLine(object.ReferenceEquals(tt1, t1));//flase 一个范围(using)内拿到同一个对象
}
}
//ITestService t = new TestServiceImp1();
//t.Name = "xw";
//t.say();
Console.Read();
ServiceCollection services = new ServiceCollection();
//ITestService 服务类型(接口) ,TestServiceImp1 实现类型
services.AddScoped<ITestService, TestServiceImp1>();
//services.AddScoped(typeof(ITestService), typeof(TestServiceImp1));
using (ServiceProvider sp = services.BuildServiceProvider())
{
//获得服务 注册什么类型拿什么类型
//GetService 如果找不到服务,就返回null
//ITestService ts1 = sp.GetService<ITestService>();
//ITestService ts1= (ITestService)sp.GetService(typeof(ITestService));//非泛型方法
//ts1.Name = "tom";
//ts1.say();
//Console.WriteLine(ts1.GetType());
// GetServices s获取全部服务
var tests = sp.GetServices<ITestService>();
foreach (var t in tests)
{
Console.WriteLine(t.GetType());
}
}
Console.ReadLine();
services.AddTransient<MQ.ISOService, MQ.SOService>();//这个<>其实就是,当代码读取到MQ.ISOService的接口的时候,会返回MQ.SOService这个对象。
- 如果要修改B 或者替换B 就必须修改A
- .。。。
public interface IMyDependency
{
void WriteMessage(string message);
}
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
//注册
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
- 不使用具体类型 MyDependency,仅使用它实现的 IMyDependency 接口。 这样可以轻松地更改实现,而无需修改控制器或 Razor 页面。
- 不创建 MyDependency 的实例,这由 DI 容器创建。
后期补:
1、MVC 控制器中可以
public IActionResult About([FromServices] IDateTime dateTime)
{
return Content($"Current server time: {dateTime.Now}");
}
2、配置文件注入
public class SampleWebSettings
{
public string Title { get; set; }
public int Updates { get; set; }
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDateTime, SystemDateTime>();
services.Configure<SampleWebSettings>(Configuration);
services.AddControllersWithViews();
}
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("samplewebsettings.json",
optional: false,
reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
{
"Title": "Title11",
"Updates": 11
}
以下代码从服务容器请求 IOptions<SampleWebSettings> 设置,并通过 Index 方法使用它们:
public class SettingsController : Controller
{
private readonly SampleWebSettings _settings;
public SettingsController(IOptions<SampleWebSettings> settingsOptions)
{
_settings = settingsOptions.Value;
}
public IActionResult Index()
{
ViewData["Title"] = _settings.Title;
ViewData["Updates"] = _settings.Updates;
return View();
}
}
<h1>@ViewData["Title"] --Title</h1>
<h1>@ViewData["Updates"] --Updates</h1>
三、原理和源码分析
扩展方法-服务构建器 (动态,可配置切换服务)
// 自定义服务类
//第一种
//services.AddTransient<IMessageService, EmailService>();
//services.AddTransient<IMessageService, SmsService>();
//第二种 封装服务注册的方法(服务构建器) 可配置选择
services.AddMessage(options=>options.UseEmail());
//扩展类 扩展Startup里面 ConfigureServices 的 IServiceCollection
public static class MessageServericeExtensions
{
public static void AddMessage(this IServiceCollection services)
{
services.AddSingleton<IMessageService, EmailService>();
}
public static void AddMessage(this IServiceCollection services,Action<MessageServiceBuider> options)
{
//创建构建器对象
var builder =new MessageServiceBuider(services);
// 调用委托,对象传过来
options(builder);
}
}
//消息服务构建器
//配置类
public class MessageServiceBuider
{
public IServiceCollection _services { get; set; }
public MessageServiceBuider(IServiceCollection services)
{
this._services = services;
}
public void UseEmail()
{
_services.AddSingleton<IMessageService, EmailService>();
}
public void UseSms()
{
_services.AddSingleton<IMessageService, SmsService>();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!