.NET中的依赖注入到底解决什么问题

在学习 .NET 开发过程中,很多开发者都会接触到一个概念:依赖注入(Dependency Injection,简称 DI)。

特别是在 ASP.NET Core 项目中,依赖注入几乎是必不可少的一部分。

但是很多初学者会有一个疑问:

为什么需要依赖注入?

以前直接 new 一个对象不是也可以正常使用吗?

实际上,依赖注入并不是为了让代码变复杂,而是为了解决项目不断扩大后,代码之间耦合越来越严重的问题。

本文将通过几个实际场景,介绍 .NET 中依赖注入解决的问题,以及它在实际项目中的使用方式。

一、传统写法存在的问题

假设我们有一个用户服务:

public class UserService

{

    public void GetUserInfo()

    {

        Console.WriteLine("获取用户信息");

    }

}

然后在业务代码中直接创建:

public class UserController

{

    private UserService userService;

    public UserController()

    {

        userService = new UserService();

    }

    public void Query()

    {

        userService.GetUserInfo();

    }

}

这种写法非常直观。

Controller 需要 UserService,那么直接 new 一个即可。

但是随着项目发展,会出现一些问题。

例如:

以后 UserService 需要连接数据库:

public class UserService

{

    private readonly Database database;

    public UserService()

    {

        database = new Database();

    }

}

然后 Database 又依赖配置文件:

public class Database

{

    private string connectionString;

    public Database()

    {

        connectionString = "数据库地址";

    }

}

最后可能形成:

Controller

UserService

Database

Configuration

每一层都需要自己创建下一层对象。

项目越大,代码之间的关系越复杂。

这就是所谓的:

高耦合。

二、依赖注入解决了对象创建的问题

依赖注入的核心思想:

不要让业务代码主动创建对象,而是由外部统一管理对象。

例如:

原来的方式:

UserController

    创建 UserService

        创建 Database

            创建 Config

变成:

.NET 容器

    创建 Config

    创建 Database

    创建 UserService

    注入 UserController

Controller 不关心 UserService 怎么创建。

它只需要声明:

“我需要一个 UserService”。

修改代码:

public class UserController

{

    private readonly UserService userService;

    public UserController(UserService service)

    {

        userService = service;

    }

    public void Query()

    {

        userService.GetUserInfo();

    }

}

可以看到:

Controller 不再负责创建 UserService。

它只负责使用。

这就是依赖注入最核心的作用:

将对象创建和业务使用分离。

三、.NET中的依赖注入容器

.NET Core 内置了依赖注入容器。

通常在 Program.cs 中注册服务。

例如:

builder.Services.AddTransient<UserService>();

表示:

当程序需要 UserService 时,自动创建一个对象。

然后 Controller 中:

public UserController(UserService service)

{

}

框架会自动传入。

常见注册方式有三种:

1. Transient(瞬时)

services.AddTransient<UserService>();

每次请求创建新的对象。

适合:

普通业务服务

无状态工具类

例如:

数据转换工具。

2. Scoped(作用域)

services.AddScoped<UserService>();

在一次请求生命周期内保持同一个对象。

适合:

Web接口

数据库上下文

例如:

一个 HTTP 请求过程中,多次访问数据库,希望使用同一个 DbContext。

3. Singleton(单例)

services.AddSingleton<UserService>();

整个程序生命周期只有一个对象。

适合:

配置管理

缓存

全局服务

例如:

设备连接管理器:

public class DeviceManager

{

}

整个程序只需要一个。

四、依赖注入最大的价值:方便替换实现

假设程序需要发送消息。

最开始:

public class MessageService

{

    public void Send()

    {

        Console.WriteLine("发送短信");

    }

}

业务代码:

var service = new MessageService();

service.Send();

如果以后需求改变:

短信改成邮件。

那么以前所有调用:

new MessageService()

都需要修改。

使用接口后:

定义:

public interface IMessageService

{

    void Send();

}

短信实现:

public class SmsService : IMessageService

{

    public void Send()

    {

        Console.WriteLine("发送短信");

    }

}

邮件实现:

public class EmailService : IMessageService

{

    public void Send()

    {

        Console.WriteLine("发送邮件");

    }

}

注册:

services.AddTransient<IMessageService, EmailService>();

业务代码:

public class OrderService

{

    private readonly IMessageService messageService;

    public OrderService(IMessageService service)

    {

        messageService = service;

    }

}

以后只需要修改注册:

services.AddTransient<IMessageService, SmsService>();

业务代码完全不用修改。

这就是依赖注入带来的扩展能力。

五、依赖注入在实际项目中的常见场景

在真实 .NET 项目中,依赖注入经常用于:

数据访问层

例如:

services.AddScoped<IUserRepository, UserRepository>();

业务层只依赖接口:

public class UserService

{

    private readonly IUserRepository repository;

    public UserService(

        IUserRepository repository)

    {

        this.repository = repository;

    }

}

以后数据库从 MySQL 换成 SQL Server。

只需要替换实现。

日志服务

例如:

services.AddLogging();

业务代码:

public class DeviceService

{

    private readonly ILogger logger;

    public DeviceService(

        ILogger<DeviceService> logger)

    {

        this.logger = logger;

    }

}

不用自己创建日志对象。

硬件设备通信

对于上位机开发,例如:

串口通信:

ISerialPortService

蓝牙通信:

IBluetoothService

设备协议解析:

IProtocolParser

都可以通过依赖注入管理。

例如:

services.AddSingleton<IProtocolParser, PacketParser>();

主程序只负责调用。

六、依赖注入是不是一定更好?

依赖注入并不是所有场景都必须使用。

如果只是一个简单的小工具:

var tool = new FileHelper();

直接创建完全没有问题。

但是当项目出现:

多个模块

多种实现

长期维护

多人协作

单元测试需求

依赖注入的优势就会越来越明显。

它本质上解决的是:

如何让大型项目中的对象管理更加清晰。

对于 .NET 开发来说,理解依赖注入不仅能够帮助开发 ASP.NET Core 项目,也能够帮助理解桌面客户端、上位机软件等大型应用的架构设计。

在后续开发 C# 上位机程序时,例如设备管理、通信模块、协议解析模块,也会经常使用这种设计方式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容