编辑
2025-09-17
C#
00

目录

依赖注入的基本概念
为什么在Winform中使用依赖注入
依赖注入容器:Microsoft.Extensions.DependencyInjection
在Winform应用中实现依赖注入
注册服务的生命周期
Singleton
Scoped
Transient
自动注册窗体和控件
日志服务的集成
配置文件示例
完整例子
安装NLog
nlog.config
Program.cs
FrmMain
总结

依赖注入的基本概念

依赖注入(Dependency Injection, DI)是一种软件设计模式,它允许我们将对象的创建与使用分离。在传统的编程方式中,当一个类需要使用另一个类的功能时,它通常会直接创建该类的实例。这种方式会导致高耦合,使得代码难以测试和维护。

依赖注入的核心思想是:类不应该负责创建它所依赖的对象,而应该从外部获取这些依赖。这样做的好处是:

  • 降低耦合:类之间的依赖关系更加松散
  • 提高可测试性:可以轻松地用模拟对象替换真实依赖
  • 增强可维护性:代码更加模块化,更容易理解和修改
  • 支持并行开发:不同团队可以独立开发不同模块

依赖注入主要有三种实现方式:

  1. 构造函数注入:通过构造函数将依赖传递给类
  2. 属性注入:通过公共属性设置依赖
  3. 方法注入:通过方法参数传递依赖

在现代C#开发中,构造函数注入是最常用的方式,也是推荐的最佳实践。

为什么在Winform中使用依赖注入

传统的Winform应用程序通常采用紧耦合的设计方式,窗体直接创建并管理它们所需的服务和对象。随着应用程序规模的增长,这种方式会导致以下问题:

  1. 代码耦合度高:窗体与具体实现紧密绑定
  2. 难以进行单元测试:无法轻松替换依赖项
  3. 代码重用性差:业务逻辑与UI逻辑混合
  4. 维护困难:修改一处可能影响多处

引入依赖注入可以解决这些问题,使Winform应用程序更加模块化、可测试和可维护。虽然Winform是一个相对较老的UI框架,但它完全可以与现代的依赖注入框架结合使用。

依赖注入容器:Microsoft.Extensions.DependencyInjection

.NET Core引入了官方的依赖注入容器Microsoft.Extensions.DependencyInjection,它提供了一套简单而强大的API来管理依赖关系。即使在Winform这样的传统.NET应用中,我们也可以使用这个现代化的DI容器。

要在Winform项目中使用它,首先需要安装相关NuGet包:

Bash
Install-Package Microsoft.Extensions.DependencyInjection

image.png

这个DI容器的核心组件包括:

  • IServiceCollection:用于注册服务
  • ServiceProvider:用于解析服务
  • ServiceDescriptor:描述服务的注册信息

它支持三种服务生命周期:

  1. Singleton:整个应用程序生命周期内只创建一个实例
  2. Scoped:在同一个作用域内共享一个实例
  3. Transient:每次请求都创建一个新实例

在Winform应用中实现依赖注入

在Winform应用中实现依赖注入需要几个关键步骤:

  1. 创建和配置服务容器
  2. 注册所需的服务
  3. 构建服务提供者
  4. 使用服务提供者获取窗体实例

下面是一个基本的实现示例:

C#
using System; using System.Windows.Forms; using Microsoft.Extensions.DependencyInjection; namespace WinformDIDemo { static class Program { public static IServiceProvider ServiceProvider { get; private set; } [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 配置依赖注入 var services = new ServiceCollection(); ConfigureServices(services); ServiceProvider = services.BuildServiceProvider(); // 使用DI容器获取主窗体实例 var mainForm = ServiceProvider.GetRequiredService<MainForm>(); Application.Run(mainForm); } private static void ConfigureServices(ServiceCollection services) { // 注册服务 services.AddScoped<IDataService, DataService>(); services.AddSingleton<IConfigService, ConfigService>(); // 注册窗体 services.AddScoped<MainForm>(); services.AddTransient<SettingsForm>(); } } }

注册服务的生命周期

在Winform应用中选择适当的服务生命周期非常重要:

Singleton

适用于整个应用程序生命周期内共享的服务,如配置服务、日志服务等。

C#
services.AddSingleton<ILogService, LogService>();

Scoped

在Winform中,Scoped生命周期通常与应用程序生命周期相同,但在某些情况下可以创建自定义范围。适用于需要在特定操作期间共享的服务。

C#
services.AddScoped<IDataService, DataService>();

Transient

每次请求都创建新实例,适用于轻量级、无状态的服务。

C#
services.AddTransient<ICalculationService, CalculationService>();

自动注册窗体和控件

在大型应用中,手动注册每个窗体和控件会很繁琐。我们可以使用反射自动注册所有窗体和控件:

C#
private static void RegisterAllFormsAndControls(ServiceCollection services) { // 获取当前应用程序集 var assembly = Assembly.GetExecutingAssembly(); // 注册所有Form类型 var formTypes = assembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface && typeof(Form).IsAssignableFrom(t)); foreach (var formType in formTypes) { services.AddScoped(formType); } // 注册所有UserControl类型 var controlTypes = assembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface && typeof(UserControl).IsAssignableFrom(t)); foreach (var controlType in controlTypes) { services.AddScoped(controlType); } }

日志服务的集成

日志记录是现代应用程序的重要组成部分。我们可以使用NLog等日志框架,并通过依赖注入集成到Winform应用中:

首先,安装必要的NuGet包:

Bash
Install-Package NLog Install-Package NLog.Extensions.Logging Install-Package Microsoft.Extensions.Logging

然后,配置日志服务:

C#
private static void ConfigureServices(ServiceCollection services) { // 配置NLog var configPath = Path.Combine(AppContext.BaseDirectory, "Configuration", "nlog.config"); LogManager.Setup().LoadConfigurationFromFile(configPath); // 注册日志服务 services.AddLogging(loggingBuilder => { loggingBuilder.ClearProviders(); loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); loggingBuilder.AddNLog(configPath); }); // 直接注册NLog的Logger services.AddSingleton(LogManager.GetCurrentClassLogger()); // 为特定类注册Logger services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); // 其他服务注册... }

配置文件示例

nlog.config文件的示例:

XML
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info" internalLogFile="logs/internal-nlog.txt"> <!-- 定义目标 --> <targets> <!-- 文件目标,用于写入日志信息 --> <target xsi:type="File" name="allfile" fileName="logs/${shortdate}.log" layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" /> <!-- 控制台目标,用于开发调试 --> <target xsi:type="Console" name="console" layout="${date:format=HH\:mm\:ss}|${level:uppercase=true}|${message} ${exception:format=tostring}" /> </targets> <!-- 定义规则 --> <rules> <!-- 所有日志记录到文件 --> <logger name="*" minlevel="Trace" writeTo="allfile" /> <!-- 在开发环境,Info级别及以上日志同时输出到控制台 --> <logger name="*" minlevel="Info" writeTo="console" /> </rules> </nlog>

完整例子

安装NLog

C#
NLog NLog.Extensions.Logging

image.png

nlog.config

XML
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info" internalLogFile="internal-nlog-info.txt"> <!-- Define targets --> <targets> <!-- JSON File Target --> <target xsi:type="File" name="logfile" fileName="${basedir}/logs/${shortdate}.json"> <layout xsi:type="JsonLayout"> <attribute name="time" layout="${longdate}" /> <attribute name="level" layout="${level:uppercase=true}" /> <attribute name="logger" layout="${logger}" /> <attribute name="message" layout="${message}" /> <attribute name="exception" layout="${exception:format=tostring}" /> </layout> </target> <!-- Console Target --> <target xsi:type="Console" name="logconsole" layout="${level:uppercase=true}: ${message} ${exception:format=tostring}" /> </targets> <!-- Define logging rules --> <rules> <!-- Log all messages with level Info and above to file --> <logger name="*" minlevel="Info" writeTo="logfile" /> <!-- Log all messages with level Debug and above to console --> <logger name="*" minlevel="Debug" writeTo="logconsole" /> </rules> </nlog>

Program.cs

C#
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using NLog; using System.Reflection; namespace AppWinformDI { internal static class Program { public static IServiceProvider ServiceProvider { get; private set; } /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); // 配置依赖注入 var services = new ServiceCollection(); ConfigureServices(services); ServiceProvider = services.BuildServiceProvider(); Application.Run(ServiceProvider.GetRequiredService<FrmMain>()); } private static void ConfigureServices(ServiceCollection services) { // 获取日志记录器 var logger = LogManager.GetCurrentClassLogger(); // 配置NLog var configPath = Path.Combine(AppContext.BaseDirectory, "Configuration", "nlog.config"); LogManager.Setup().LoadConfigurationFromFile(configPath); // 注册NLog服务 services.AddLogging(loggingBuilder => { loggingBuilder.ClearProviders(); loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); loggingBuilder.AddNLog(configPath); }); // 直接注册NLog的Logger services.AddSingleton(LogManager.GetCurrentClassLogger()); // 为常用类注册特定的Logger services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); // 注册窗体 RegisterAllFormsAndControls(services); } private static void RegisterAllFormsAndControls(ServiceCollection services) { // 获取当前应用程序集 var assembly = Assembly.GetExecutingAssembly(); // 查找所有继承自Form的类型 var formTypes = assembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface && typeof(System.Windows.Forms.Form).IsAssignableFrom(t)); // 查找所有继承自UserControl的类型 var controlTypes = assembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface && typeof(System.Windows.Forms.UserControl).IsAssignableFrom(t)); // 注册所有Form类型 foreach (var formType in formTypes) { services.AddScoped(formType); } // 注册所有UserControl类型 foreach (var controlType in controlTypes) { services.AddScoped(controlType); } } } }

FrmMain

C#
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace AppWinformDI { public partial class FrmMain : Form { private readonly IServiceProvider _serviceProvider; private readonly ILogger<FrmMain> _logger; public FrmMain(IServiceProvider serviceProvider, ILogger<FrmMain> logger) { InitializeComponent(); _serviceProvider = serviceProvider; _logger = logger; } private void btnSetting_Click(object sender, EventArgs e) { var setting= _serviceProvider.GetRequiredService<FrmSetting>(); setting.ShowDialog(); } } }

总结

本文全面介绍了在C# Winform应用中实现依赖注入的方法,从基本概念到实际实现,再到最佳实践。主要内容包括:

  1. 依赖注入的基本概念:理解DI的原则和好处
  2. Microsoft.Extensions.DependencyInjection的使用利用.NET Core的DI容器
  3. 服务生命周期管理:理解Singleton、Scoped和Transient的区别
  4. 自动注册窗体和控件:使用反射简化注册过程
  5. 日志服务集成:通过NLog提供全面的日志记录能力
  6. 完整实现案例:展示了实际应用中的依赖注入实现
  7. 最佳实践与注意事项:帮助避免常见陷阱

通过引入依赖注入,我们可以使Winform应用程序更加模块化、可测试和可维护。虽然Winform是一个传统的UI框架,但结合现代的依赖注入技术,可以显著提高应用程序的质量和开发效率。

对于新项目,强烈建议从一开始就采用依赖注入;对于现有项目,可以逐步引入依赖注入,先从核心服务开始,然后扩展到整个应用程序。

通过本指南的实践,您将能够在Winform应用中充分利用依赖注入的优势,创建更加健壮、可维护的应用程序。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!