前言
依赖注入(Dependency Injection,简称DI)是一种软件设计模式,主要用于管理和组织一个软件系统中不同模块之间的依赖关系。
在依赖注入中,依赖项(也称为组件或服务)不是在代码内部创建或查找的,而是由外部系统提供给组件。
具体来说,当某个角色(如一个 C# 实例,调用者)需要另一个角色(如另一个 C# 实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。
但在依赖注入中,创建被调用者的工作不再由调用者来完成,而是由外部容器来完成,并注入给调用者。
依赖注入可以提高代码的可维护性、可测试性、可替换性和可扩展性,降低组件之间的耦合度,使得代码更加清晰和灵活,以前我们写过在 Asp.NET Core Web API 项目中如何通过内置的依赖注入容器使用依赖注入的文章《一个简单的 ASP.NET Core 依赖注入例子,提高代码的可维护性和可扩展性》,今天继续分享一个在 .NET Core Console 项目使用依赖注入的详细例子,大家可以比较两者之间的不同。
Step By Step 步骤
- 创建一个 .NET Core Console 项目
- 从 Nuget 安装以下包
Microsoft.Extensions.DependencyInjection
System.Data.SqlClient
3.创建 User 实体类:
record User(long Id, string UserName, string Password);
4.创建接口 IUserBiz 和 IUserDAO 及其实现类(留意注释)
interface IUserBiz { /// <summary> /// 检查用户名、密码是否匹配 /// </summary> /// <param name="userName"></param> /// <param name="password"></param> /// <returns></returns> public bool CheckLogin(string userName, string password); } interface IUserDAO { /// <summary> /// 查询用户名为userName的用户信息 /// </summary> /// <param name="userName"></param> /// <returns></returns> public User? GetByUserName(string userName); } class UserBiz : IUserBiz { private readonly IUserDAO userDAO; // 通过构造方法要求注入 IUserDAO 服务 public UserBiz(IUserDAO userDAO) { this.userDAO = userDAO; } public bool CheckLogin(string userName, string password) { var user = userDAO.GetByUserName(userName); if (user == null) { return false; } else { return user.Password == password; } } } class UserDAO : IUserDAO { private readonly IDbConnection conn; // 通过构造方法要求依赖注入容器为其注入一个 IDbConnection 对象 public UserDAO(IDbConnection conn) { this.conn = conn; } public User? GetByUserName(string userName) { using var dt = SqlHelper.ExecuteQuery(conn, $"select * from T_Users where UserName={userName}"); if (dt.Rows.Count <= 0) { return null; } DataRow row = dt.Rows[0]; int id = (int)row["Id"]; string uname = (string)row["UserName"]; string password = (string)row["Password"]; return new User(id, uname, password); } }
5.创建数据库操作帮助类 SqlHelper
using System.Data; static class SqlHelper { // 执行查询语句并返回 DataTable public static DataTable ExecuteQuery(this IDbConnection conn, FormattableString formattable) { using IDbCommand cmd = CreateCommand(conn, formattable); DataTable dt = new DataTable(); using var reader = cmd.ExecuteReader(); dt.Load(reader); return dt; } // 执行查询语句并返回一个值 public static object? ExecuteScalar(this IDbConnection conn, FormattableString formattable) { using IDbCommand cmd = CreateCommand(conn, formattable); return cmd.ExecuteScalar(); } // 执行 DML 语句 public static int ExecuteNonQuery(this IDbConnection conn, FormattableString formattable) { using IDbCommand cmd = CreateCommand(conn, formattable); int result = cmd.ExecuteNonQuery(); return result; } // 创建 Command private static IDbCommand CreateCommand(IDbConnection conn, FormattableString formattable) { var cmd = conn.CreateCommand(); string sql = formattable.Format; for (int i = 0; i < formattable.ArgumentCount; i++) { sql = sql.Replace("{" + i + "}", "@p" + i); var parameter = cmd.CreateParameter(); parameter.ParameterName = "@p" + i; parameter.Value = formattable.GetArgument(i); cmd.Parameters.Add(parameter); } cmd.CommandText = sql; return cmd; } }
6.打开 Program.cs,引入依赖注入命名空间
using Microsoft.Extensions.DependencyInjection;
7.创建依赖注入容器
// 2. 创建用于注册服务的容器 ServiceCollection services = new ServiceCollection(); ``
8.注入服务(留意注释)
// 3.1 注入 IDbConnection 服务(范围) services.AddScoped<IDbConnection>(sp => { string connStr = "Server=(localdb)\\mssqllocaldb;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true"; var conn = new SqlConnection(connStr); conn.Open(); return conn; }); // 3.2 注入 IUserDAO 和 IUserBiz 服务(范围) // ----把UserDAO注册为IUserDAO服务的实现类 // ----实现类的参数也会一起注入 services.AddScoped<IUserDAO, UserDAO>(); services.AddScoped<IUserBiz, UserBiz>();
9.使用(留意注释)
// 调用 IServiceCollection 的 BuildServiceProvider 方法创建一个 ServiceProvider 对象 using (ServiceProvider sp = services.BuildServiceProvider()) { // 调用 GetRequiredService 方法获取服务 var userBiz = sp.GetRequiredService<IUserBiz>(); bool b = userBiz.CheckLogin("jacky", "123456"); Console.WriteLine(b); }
结语
依赖注入的实现方式有多种,如构造注入、属性注入和接口注入等。
通过依赖注入,可以更容易地管理和维护系统的各个组件,轻松地将模拟依赖注入到单元测试中,更灵活地添加新功能或替换现有组件。
虽然依赖注入在软件开发中有很多优点,但在使用时也需要谨慎,以确保正确地管理和配置依赖关系,避免潜在的问题,比如违背单一职责原则等设计原则,导致代码结构混乱,维护成本增加等。
附录:完整的 Program.cs 代码(留意注释)
using DI魅力渐显_依赖注入; // 1. 引用依赖注入命名空间 using Microsoft.Extensions.DependencyInjection; using System.Data; using System.Data.SqlClient; // 2. 创建用于注册服务的容器 ServiceCollection services = new ServiceCollection(); // 3.1 注入 IDbConnection 服务(范围) services.AddScoped<IDbConnection>(sp => { string connStr = "Server=(localdb)\\mssqllocaldb;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true"; var conn = new SqlConnection(connStr); conn.Open(); return conn; }); // 3.2 注入 IUserDAO 和 IUserBiz 服务(范围) // ----把UserDAO注册为 IUserDAO 服务的实现类 // ----实现类的参数也会一起注入 services.AddScoped<IUserDAO, UserDAO>(); services.AddScoped<IUserBiz, UserBiz>(); // 4. 使用 // 调用 IServiceCollection 的 BuildServiceProvider 方法创建一个 ServiceProvider 对象 using (ServiceProvider sp = services.BuildServiceProvider()) { // 调用 GetRequiredService 方法获取服务 var userBiz = sp.GetRequiredService<IUserBiz>(); bool b = userBiz.CheckLogin("jacky", "123456"); Console.WriteLine(b); }
我是老杨,一个奋斗在一线的资深研发老鸟,让我们一起聊聊技术,聊聊人生。
都看到这了,求个点赞、关注、在看三连呗,感谢支持。