商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 全面解析.NET中的依赖注入(DI)

全面解析.NET中的依赖注入(DI)

  发布于2026-07-23 阅读(0)

扫一扫,手机访问

一、依赖注入核心原理

1. 控制反转(IoC)与DI关系

  • 控制反转(IoC):简单说就是——框架说了算,流程由框架来掌控,开发者不再大包大揽。
  • 依赖注入(DI):这是IoC的一种具体实现手段,核心思想是:对象所需的外部依赖,由外部容器提供,而不是自己主动去创建。

2. .NET DI核心组件

  • IServiceCollection:服务注册的“容器”,用来存放你声明的各种服务。
  • IServiceProvider:服务的“解析器”,你需要什么服务,找它要就行。
  • ServiceDescriptor:服务的“身份证”,里面记录了服务的类型、实现类型以及它的生命周期信息。

二、服务生命周期

生命周期是DI里一个绕不开的话题。对于同一个服务,不同生命周期的行为差别很大。下面这张表帮你快速理清,这三者到底对应什么场景。

生命周期描述适用场景
Transient每次请求都会创建一个全新的实例轻量级、无状态的服务
Scoped同一个作用域内(比如一个Web请求)共享同一个实例Web请求相关的上下文
Singleton全局范围内,只有一个实例,一直复用配置服务、缓存等全局共享的数据
// 注册示例services.AddTransient();services.AddScoped();services.AddSingleton();

所以说,选择合适的生命周期,可不是随便选选就算了的。用错了,轻则性能下降,重则出现数据混乱的问题。

三、DI容器实现原理

1. 服务注册流程

注册的底层逻辑其实很简单,就是把服务的类型、实现类型、生命周期封装成一个描述符,然后放进一个大“集合”里。

public static IServiceCollection AddTransient(this IServiceCollection services){    // 创建服务描述符    var descriptor = new ServiceDescriptor(        typeof(TService),        typeof(TImplementation),        ServiceLifetime.Transient);        // 添加到集合    services.Add(descriptor);    return services;}

2. 服务解析流程

从容器里拿出一个服务时,背后经历了一个标准的查找和实例化流程。

public object GetService(Type serviceType){    // 1. 查找服务描述符    var descriptor = _descriptors.FirstOrDefault(d => d.ServiceType == serviceType);        // 2. 根据生命周期创建实例    if (descriptor.Lifetime == ServiceLifetime.Singleton)    {        if (_singletons.TryGetValue(serviceType, out var instance))            return instance;                instance = CreateInstance(descriptor);        _singletons[serviceType] = instance;        return instance;    }    // ...处理Scoped和Transient}

四、高级实现方法

除了最基础的直接注册,还有几种更灵活的方式,在实际开发中经常用到。

1. 工厂模式注册

services.AddTransient(provider => {    var otherService = provider.GetRequiredService();    return new ServiceImpl(otherService, "参数");});

2. 泛型服务注册

services.AddTransient(typeof(IRepository<>), typeof(Repository<>));

3. 多实现解决方案

// 注册多个实现services.AddTransient();services.AddTransient();// 解析时获取所有实现var services = provider.GetServices();

上面这几种模式,在应对不同业务策略或条件分支时,非常实用。

五、ASP.NET Core中的DI集成

在ASP.NET Core里,DI可以说是无处不在,比如控制器、视图、甚至中间件,都可以直接用依赖注入来获取服务。

1. 控制器注入

public class HomeController : Controller{    private readonly ILogger _logger;        public HomeController(ILogger logger)    {        _logger = logger; // 自动注入    }}

2. 视图注入

@inject IConfiguration Config

当前环境: @Config["Environment"]

3. 中间件注入

public class CustomMiddleware{    private readonly RequestDelegate _next;    private readonly ILogger _logger;        public CustomMiddleware(        RequestDelegate next,        ILogger logger)    {        _next = next;        _logger = logger;    }        public async Task InvokeAsync(HttpContext context)    {        // 使用注入的服务        _logger.LogInformation("中间件执行");        await _next(context);    }}

六、自定义DI容器实现

学习DI最好的方式,就是自己动手实现一个简化版。这能帮你真正想明白注册和解析的背后发生了什么。

1. 简易DI容器实现

public class SimpleContainer : IServiceProvider{    private readonly Dictionary _descriptors;        public SimpleContainer(IEnumerable descriptors)    {        _descriptors = descriptors.ToDictionary(x => x.ServiceType);    }        public object GetService(Type serviceType)    {        if (!_descriptors.TryGetValue(serviceType, out var descriptor))            return null;                    if (descriptor.ImplementationInstance != null)            return descriptor.ImplementationInstance;                    var type = descriptor.ImplementationType ?? descriptor.ServiceType;        return ActivatorUtilities.CreateInstance(this, type);    }}

2. 属性注入实现

public static class PropertyInjectionExtensions{    public static void AddPropertyInjection(this IServiceCollection services)    {        services.AddTransient();    }}public class PropertyInjectionStartupFilter : IStartupFilter{    public Action Configure(Action next)    {        return builder =>        {            builder.Use(async (context, nextMiddleware) =>            {                var endpoint = context.GetEndpoint();                if (endpoint?.Metadata.GetMetadata() is { } descriptor)                {                    var controller = context.RequestServices.GetRequiredService(descriptor.ControllerTypeInfo);                    // 反射实现属性注入                    InjectProperties(controller, context.RequestServices);                }                await nextMiddleware();            });            next(builder);        };    }        private void InjectProperties(object target, IServiceProvider services)    {        var properties = target.GetType().GetProperties()            .Where(p => p.CanWrite && p.GetCustomAttribute() != null);                    foreach (var prop in properties)        {            var service = services.GetService(prop.PropertyType);            if (service != null)                prop.SetValue(target, service);        }    }}

七、最佳实践

1. 服务设计原则

  • 遵循显式依赖原则:明确告诉别人你需要什么,而不是藏着掖着。
  • 避免服务定位器模式:这是业界的公认“反模式”,别用。
  • 保持服务轻量级:每个服务的职责要单一,不要让它太“重”。

2. 常见陷阱

这里有个很经典的坑:把Scoped服务“捕获”到Singleton里。这样做会导致Scoped服务被长时间持有,产生意料之外的生命周期错乱。

// 错误示例:捕获Scoped服务到Singleton中services.AddSingleton(provider => {    var scopedService = provider.GetRequiredService(); // 危险!    return new BackgroundService(scopedService);});// 正确做法:使用IServiceScopeFactoryservices.AddSingleton(provider => {    var scopeFactory = provider.GetRequiredService();    return new BackgroundService(scopeFactory);});

八、性能优化

1. 避免过度注入

有时候一个构造函数里塞了七八个参数,这其实是不好的设计。一个类如果依赖太多东西,说明它的职责可能过重了。

// 不好:注入过多服务public class OrderService(    ILogger logger,    IEmailService emailService,    ISmsService smsService,    IRepository repo,    ICache cache,    IConfig config){    // ...}// 改进:使用聚合服务public class OrderService(    ILogger logger,    INotificationService notification,    IOrderInfrastructure infra){    // ...}

2. 编译时注入

对于性能要求极高的场景,还可以考虑使用Source Generator在编译阶段就生成注册代码,避免运行时反射带来的性能开销。

[RegisterTransient(typeof(IMyService))]public class MyService : IMyService{    // ...}// 使用Source Generator自动生成注册代码static partial class ServiceRegistration{    static partial void AddGeneratedServices(IServiceCollection services)    {        services.AddTransient();    }}

总的来说,.NET的依赖注入系统是框架的核心基础设施。只有真正理解它的原理和实现方式,才能编写出更可测试、更松耦合的应用程序。从基础概念到高级用法,再到实际开发中的注意事项,都能帮你少走很多弯路。

本文转载于:https://www.jb51.net/aspnet/344256bdy.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注