依赖注入(Dependency Injection, DI)是一种软件设计模式,它允许我们将对象的创建与使用分离。在传统的编程方式中,当一个类需要使用另一个类的功能时,它通常会直接创建该类的实例。这种方式会导致高耦合,使得代码难以测试和维护。
依赖注入的核心思想是:类不应该负责创建它所依赖的对象,而应该从外部获取这些依赖。这样做的好处是:
依赖注入主要有三种实现方式:
在现代C#开发中,构造函数注入是最常用的方式,也是推荐的最佳实践。
传统的Winform应用程序通常采用紧耦合的设计方式,窗体直接创建并管理它们所需的服务和对象。随着应用程序规模的增长,这种方式会导致以下问题:
引入依赖注入可以解决这些问题,使Winform应用程序更加模块化、可测试和可维护。虽然Winform是一个相对较老的UI框架,但它完全可以与现代的依赖注入框架结合使用。
.NET Core引入了官方的依赖注入容器Microsoft.Extensions.DependencyInjection
,它提供了一套简单而强大的API来管理依赖关系。即使在Winform这样的传统.NET应用中,我们也可以使用这个现代化的DI容器。
要在Winform项目中使用它,首先需要安装相关NuGet包:
BashInstall-Package Microsoft.Extensions.DependencyInjection
这个DI容器的核心组件包括:
它支持三种服务生命周期:
在Winform应用中实现依赖注入需要几个关键步骤:
下面是一个基本的实现示例:
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应用中选择适当的服务生命周期非常重要:
适用于整个应用程序生命周期内共享的服务,如配置服务、日志服务等。
C#services.AddSingleton<ILogService, LogService>();
在Winform中,Scoped生命周期通常与应用程序生命周期相同,但在某些情况下可以创建自定义范围。适用于需要在特定操作期间共享的服务。
C#services.AddScoped<IDataService, DataService>();
每次请求都创建新实例,适用于轻量级、无状态的服务。
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包:
BashInstall-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>
C#NLog NLog.Extensions.Logging
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>
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);
}
}
}
}
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应用中实现依赖注入的方法,从基本概念到实际实现,再到最佳实践。主要内容包括:
通过引入依赖注入,我们可以使Winform应用程序更加模块化、可测试和可维护。虽然Winform是一个传统的UI框架,但结合现代的依赖注入技术,可以显著提高应用程序的质量和开发效率。
对于新项目,强烈建议从一开始就采用依赖注入;对于现有项目,可以逐步引入依赖注入,先从核心服务开始,然后扩展到整个应用程序。
通过本指南的实践,您将能够在Winform应用中充分利用依赖注入的优势,创建更加健壮、可维护的应用程序。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!