WPF/C#:在WPF中如何实现依赖注入

前言

本文通过 WPF Gallery 这个项目学习依赖注入的相关概念与如何在WPF中进行依赖注入。

什么是依赖注入

依赖注入(Dependency Injection,简称DI)是一种设计模式,用于实现控制反转(Inversion of Control,简称IoC)原则。依赖注入的主要目的是将对象的创建和对象之间的依赖关系的管理从对象内部转移到外部容器或框架中,从而提高代码的可维护性、可测试性和灵活性。

依赖注入的核心概念

  1. 依赖:一个对象需要另一个对象来完成其工作,那么前者就依赖于后者。例如,一个OrderService类可能依赖于一个ProductRepository类来获取产品信息。
  2. 注入:将依赖的对象传递给需要它的对象,而不是让需要它的对象自己去创建依赖的对象。注入可以通过构造函数、属性或方法参数来实现。
  3. 容器:一个管理对象创建和依赖关系的框架或库。容器负责实例化对象,解析依赖关系,并将依赖的对象注入到需要它们的对象中。

依赖注入的类型

构造函数注入:依赖的对象通过类的构造函数传递。

public class OrderService
{private readonly IProductRepository _productRepository;public OrderService(IProductRepository productRepository){_productRepository = productRepository;}
}

属性注入:依赖的对象通过类的公共属性传递。

public class OrderService
{public IProductRepository ProductRepository { get; set; }
}

方法注入:依赖的对象通过类的方法参数传递。

public class OrderService
{public void ProcessOrder(IProductRepository productRepository){// 使用 productRepository 处理订单}
}

为什么要进行依赖注入

依赖注入(Dependency Injection,简称DI)是一种设计模式,通过它可以将对象的创建和对象之间的依赖关系的管理从对象内部转移到外部容器或框架中。进行依赖注入有以下几个重要的原因和优点:

  1. 降低耦合度: 依赖注入通过将依赖关系的管理从对象内部转移到外部容器,使得对象不需要知道如何创建其依赖的对象,只需要知道依赖对象的接口。这样可以显著降低对象之间的耦合度,使得代码更加模块化和灵活。
  2. 提高可测试性: 依赖注入使得单元测试变得更加容易和高效。通过使用模拟对象(Mock Object)或存根(Stub)来替代实际的依赖对象,开发者可以在不依赖于实际实现的情况下进行单元测试。这有助于确保测试的独立性和可靠性。
  3. 提高可维护性: 由于依赖注入降低了对象之间的耦合度,代码变得更加模块化和清晰。这使得代码更容易理解和维护。当需要修改或替换某个依赖对象时,只需要修改配置或注册信息,而不需要修改使用该对象的代码。
  4. 提高灵活性: 依赖注入使得系统更加灵活,能够轻松地替换依赖的对象,从而实现不同的功能或行为。例如,可以通过配置文件或代码来切换不同的数据库访问层实现,而不需要修改业务逻辑代码。
  5. 促进关注点分离: 依赖注入有助于实现关注点分离(Separation of Concerns),使得每个对象只需要关注自己的职责,而不需要关心如何创建和获取其依赖的对象。这有助于提高代码的清晰度和可维护性。
  6. 支持设计模式和最佳实践: 依赖注入是许多设计模式和最佳实践的基础,如控制反转(Inversion of Control,简称IoC)、服务定位器模式(Service Locator Pattern)等。通过使用依赖注入,开发者可以更容易地实现这些模式和实践,从而提高代码的质量和可扩展性。

如何实现依赖注入

本文通过 WPF Gallery 项目学习在WPF中如何使用依赖注入,代码地址:

https://github.com/microsoft/WPF-Samples/blob/main/SampleApplications/WPFGallery

这个项目中实现依赖注入,使用到了这两个包:

image-20240711100435001

首先查看App.xaml.cs中的内容:

public partial class App : Application
{private static readonly IHost _host = Host.CreateDefaultBuilder().ConfigureServices((context, services) =>{services.AddSingleton<INavigationService, NavigationService>();services.AddSingleton<MainWindow>();services.AddSingleton<MainWindowViewModel>();services.AddTransient<DashboardPage>();services.AddTransient<DashboardPageViewModel>();services.AddTransient<ButtonPage>();services.AddTransient<ButtonPageViewModel>();services.AddTransient<CheckBoxPage>();services.AddTransient<CheckBoxPageViewModel>();services.AddTransient<ComboBoxPage>();services.AddTransient<ComboBoxPageViewModel>();services.AddTransient<RadioButtonPage>();services.AddTransient<RadioButtonPageViewModel>();services.AddTransient<SliderPage>();services.AddTransient<SliderPageViewModel>();services.AddTransient<CalendarPage>();services.AddTransient<CalendarPageViewModel>();services.AddTransient<DatePickerPage>();services.AddTransient<DatePickerPageViewModel>();services.AddTransient<TabControlPage>();services.AddTransient<TabControlPageViewModel>();services.AddTransient<ProgressBarPage>();services.AddTransient<ProgressBarPageViewModel>();services.AddTransient<MenuPage>();services.AddTransient<MenuPageViewModel>();services.AddTransient<ToolTipPage>();services.AddTransient<ToolTipPageViewModel>();services.AddTransient<CanvasPage>();services.AddTransient<CanvasPageViewModel>();services.AddTransient<ExpanderPage>();services.AddTransient<ExpanderPageViewModel>();services.AddTransient<ImagePage>();services.AddTransient<ImagePageViewModel>();services.AddTransient<DataGridPage>();services.AddTransient<DataGridPageViewModel>();services.AddTransient<ListBoxPage>();services.AddTransient<ListBoxPageViewModel>();services.AddTransient<ListViewPage>();services.AddTransient<ListViewPageViewModel>();services.AddTransient<TreeViewPage>();services.AddTransient<TreeViewPageViewModel>();services.AddTransient<LabelPage>();services.AddTransient<LabelPageViewModel>();services.AddTransient<TextBoxPage>();services.AddTransient<TextBoxPageViewModel>();services.AddTransient<TextBlockPage>();services.AddTransient<TextBlockPageViewModel>();services.AddTransient<RichTextEditPage>();services.AddTransient<RichTextEditPageViewModel>();services.AddTransient<PasswordBoxPage>();services.AddTransient<PasswordBoxPageViewModel>();services.AddTransient<ColorsPage>();services.AddTransient<ColorsPageViewModel>();services.AddTransient<LayoutPage>();services.AddTransient<LayoutPageViewModel>();services.AddTransient<AllSamplesPage>();services.AddTransient<AllSamplesPageViewModel>();services.AddTransient<BasicInputPage>();services.AddTransient<BasicInputPageViewModel>();services.AddTransient<CollectionsPage>();services.AddTransient<CollectionsPageViewModel>();services.AddTransient<MediaPage>();services.AddTransient<MediaPageViewModel>();services.AddTransient<NavigationPage>();services.AddTransient<NavigationPageViewModel>();services.AddTransient<TextPage>();services.AddTransient<TextPageViewModel>();services.AddTransient<DateAndTimePage>();services.AddTransient<DateAndTimePageViewModel>();services.AddTransient<StatusAndInfoPage>();services.AddTransient<StatusAndInfoPageViewModel>();services.AddTransient<SamplesPage>();services.AddTransient<SamplesPageViewModel>();services.AddTransient<DesignGuidancePage>();services.AddTransient<DesignGuidancePageViewModel>();services.AddTransient<UserDashboardPage>();services.AddTransient<UserDashboardPageViewModel>();services.AddTransient<TypographyPage>();services.AddTransient<TypographyPageViewModel>();services.AddSingleton<IconsPage>();services.AddSingleton<IconsPageViewModel>();services.AddSingleton<SettingsPage>();services.AddSingleton<SettingsPageViewModel>();services.AddSingleton<AboutPage>();services.AddSingleton<AboutPageViewModel>();}).Build();[STAThread]public static void Main(){_host.Start();App app = new();app.InitializeComponent();app.MainWindow = _host.Services.GetRequiredService<MainWindow>();app.MainWindow.Visibility = Visibility.Visible;app.Run();}
}

image-20240711083011393

IHost是什么?

在C#中,IHost 是一个接口,它是.NET 中用于构建和配置应用程序的Host的概念的抽象。IHost接口定义了启动、运行和管理应用程序所需的服务和组件的集合。它通常用于ASP.NET Core应用程序,但也适用于其他类型的.NET 应用程序,如控制台应用程序或WPF程序。

image-20240711082817268

IHost接口由HostBuilder类实现,它提供了创建和配置IHost实例的方法。HostBuilder允许你添加各种服务,如日志记录、配置、依赖注入容器等,并配置应用程序的启动和停止行为。

image-20240711083048854

image-20240711083156306

提供了用于使用预配置默认值创建Microsoft.Extensions.Hosting.IHostBuilder实例的方便方法。

image-20240711083713204

返回一个IHostBuilder。

image-20240711084145756

image-20240711084211035

向容器中添加服务。此操作可以调用多次,其结果是累加的。

参数configureDelegate的含义是配置Microsoft.Extensions.DependencyInjection.IServiceCollection的委托,
该集合将用于构造System.IServiceProvider。

该委托需要两个参数类型分别为HostBuilderContext、IServiceCollection没有返回值。

image-20240711084853971

这里传入了一个满足该委托类型的Lambda表达式。

在C#中,() => {}是一种Lambda表达式的语法。Lambda表达式是一种轻量级的委托包装器,它可以让你定义一个匿名方法,并将其作为参数传递给支持委托或表达式树的方法。

Lambda表达式提供了一种简洁的方式来定义方法,特别是在需要将方法作为参数传递给其他方法时,它们非常有用。

image-20240711085344696

在添加服务,这里出现了两种生命周期,除了AddSingleton、AddTransient外还有AddScoped。

这些方法定义了服务的生命周期,即服务实例在应用程序中的创建和管理方式。

AddSingleton

  • 生命周期:单例(Singleton)
  • 含义:在整个应用程序生命周期内,只创建一个服务实例。无论从容器中请求多少次,都会返回同一个实例。
  • 适用场景:适用于无状态服务,或者在整个应用程序中共享的资源,如配置、日志记录器等。

AddTransient

  • 生命周期:瞬时(Transient)
  • 含义:每次从容器中请求服务时,都会创建一个新的实例。
  • 适用场景:适用于有状态的服务,或者每次请求都需要一个新的实例的场景,如页面、视图模型等。

AddScoped

  • 生命周期:作用域(Scoped)
  • 含义:在每个作用域内,服务实例是唯一的。作用域通常与请求的生命周期相关联,例如在Web应用程序中,每个HTTP请求会创建一个新的作用域。
  • 适用场景:适用于需要在请求范围内共享实例的服务,如数据库上下文。

使用这些服务

在Main函数中:

image-20240711095100016

启动_host,通过_host.Services.GetRequiredService<MainWindow>();获取MainWindow实例。

以MainWindow类为例,查看MainWindow.xaml.cs中MainWindow的构造函数:

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{_serviceProvider = serviceProvider;ViewModel = viewModel;DataContext = this;InitializeComponent();Toggle_TitleButtonVisibility();_navigationService = navigationService;_navigationService.Navigating += OnNavigating;_navigationService.SetFrame(this.RootContentFrame);_navigationService.Navigate(typeof(DashboardPage));WindowChrome.SetWindowChrome(this,new WindowChrome{CaptionHeight = 50,CornerRadius = default,GlassFrameThickness = new Thickness(-1),ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),UseAeroCaptionButtons = true});this.StateChanged += MainWindow_StateChanged;
}

去掉与本主题无关的内容之后,如下所示:

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{_serviceProvider = serviceProvider;ViewModel = viewModel; _navigationService = navigationService;  
}

有没有发现不用自己new这些对象了,这些对象的创建由依赖注入容器来管理,在需要这些对象的时候,像现在这样通过构造函数中注入即可。

如果没有用依赖注入,可能就是这样子的:

public MainWindow()
{_serviceProvider = new IServiceProvider();ViewModel = new MainWindowViewModel(); _navigationService = new INavigationService();  
}

总结

本文先介绍依赖注入的概念,再解释为什么要进行依赖注入,最后通过 WPF Gallery 这个项目学习如何在WPF中使用依赖注入。

参考

1、[WPF-Samples/Sample Applications/WPFGallery at main · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Sample Applications/WPFGallery)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/47453.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Redis]典型应用——缓存

什么是缓存 缓存&#xff08;Cache&#xff09;是一种用于临时存储数据的机制&#xff0c;目的是提高数据访问速度和系统性能。 核心思路就是把一些常用的数据放到触手可及(访问速度更快)的地方&#xff0c;方便随时读取 缓存是一个相对的概念&#xff0c;比如说&#xff0c…

EE trade:强平和爆仓的区别

在金融交易市场中&#xff0c;杠杆交易的引入&#xff0c;让投资者可以用少量的资金撬动更大的头寸&#xff0c;获取更大的收益。然而&#xff0c;杠杆交易也带来了更大的风险&#xff0c;一旦市场波动&#xff0c;投资者可能会面临强平或爆仓的风险。了解强平和爆仓的区别&…

选择Maya进行3D动画制作与渲染的理由

如果你对3D动画充满热情并追求成为专业3D动画师的梦想&#xff0c;你一定听说过Maya——近年来3D动画的行业标准。Maya被3D艺术家广泛使用&#xff0c;你是否想知道为什么Maya总是他们的首选&#xff1f;下面一起来了解下。 一、什么是Maya&#xff1f; 由Autodesk开发的Maya是…

2024年土木建筑与结构工程国际会议(IACCASE 2024)

2024年土木建筑与结构工程国际会议 2024 International Conference on Civil and Structural Engineering 【1】会议简介 2024年土木建筑与结构工程国际会议旨在为全球土木建筑与结构工程领域的专家学者、研究人员及从业人员提供一个交流与合作的平台。会议聚焦该领域的最新研究…

影院选座系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;影院信息管理&#xff0c;电影类型管理&#xff0c;放映厅管理&#xff0c;电影信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;影院信息&…

学习小记-Nacos的服务注册与发现原理

服务注册&#xff1a; 当一个服务实例启动时&#xff0c;它会向 Nacos 服务器注册自己的信息&#xff0c;包括 IP 地址、端口号、元数据&#xff08;如服务版本、区域信息等&#xff09;。服务实例使用 Nacos API 发送注册请求&#xff0c;Nacos 服务器接收请求并存储服务实例信…

[iOS]浅析isa指针

[iOS]浅析isa指针 文章目录 [iOS]浅析isa指针isa指针isa的结构isa的初始化注意事项 上一篇留的悬念不止分类的实现 还有isa指针到底是什么 它是怎么工作的 class方法又是怎么运作的 class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 这里面的class又是何方…

python的tkinter、socket库开发tcp的客户端和服务端

一、tcp通讯流程和开发步骤 1、tcp客户端和服务端通讯流程图 套接字是通讯的利器&#xff0c;连接时要经过三次握手建立连接&#xff0c;断开连接要经过四次挥手断开连接。 2、客户端开发流程 1&#xff09;创建客户端套接字 2&#xff09;和服务端器端套接字建立连接 3&#x…

Linux·基本指令(下)

1. mv 指令 (move) 语法&#xff1a;mv[选项] 源文件或目录 目标文件或目录 功能&#xff1a;将源文件或目录剪贴到一个新位置&#xff0c;或给源文件或目录改名但不会改变其内容 常用选项&#xff1a; -f &#xff1a;force 强制&#xff0c;如果目标文件已经存在&#xff0c;…

HCIE-AI大模型直通车火热报名中

第一阶段&#xff1a;HCIA-AI Solution Architect&#xff08;直播&#xff0c;39课时&#xff09; 该阶段详细介绍 AI 大模型所需基础技术栈&#xff0c;包含深度学习基础、计算机视觉技术、自然语言处理技术、华为开源深度学习框架 MindSpore、注意力制、Transformer 架构&am…

Spock单元测试框架使用介绍和实践

背景 单元测试是保证我们写的代码是我们想要的结果的最有效的办法。根据下面的数据图统计&#xff0c;单元测试从长期来看也有很大的收益。 单元测试收益: 它是最容易保证代码覆盖率达到100%的测试。可以⼤幅降低上线时的紧张指数。单元测试能更快地发现问题。单元测试的性…

【spring boot】初学者项目快速练手

一小时带你从0到1实现一个SpringBoot项目开发_哔哩哔哩_bilibili 一、简介 二、项目结构 三、代码结构 1.生成框架 Spring Initializr 快速生成一个初始的项目代码&#xff0c;会生成一个demo文件 打开intellj idea&#xff0c;导入demo文件 2.目录结构 源码都放在src-ma…

“论软件维护方法及其应用”精选范文,软考高级论文,系统架构设计师论文

论文真题 软件维护是指在软件交付使用后&#xff0c;直至软件被淘汰的整个时间范围内&#xff0c;为了改正错误或满足 新的需求而修改软件的活动。在软件系统运行过程中&#xff0c;软件需要维护的原因是多种多样的&#xff0c; 根据维护的原因不同&#xff0c;可以将软件维护…

Lua基础知识入门

1 基础知识 标识符&#xff1a;标识符的定义和 C语言相同&#xff1a;字母和下划线_ 开头&#xff0c; 下划线_ 大写字母一般是lua保留字&#xff0c; 如_VERSION 全局变量&#xff1a;默认情况下&#xff0c;变量总是认为是全局的&#xff0c;不需要申明&#xff0c;给一个变…

电脑压缩视频文件 电脑压缩视频大小的方法

在数字化时代&#xff0c;视频已成为我们记录生活、分享快乐的重要工具。然而&#xff0c;大尺寸的视频文件常常让分享和存储变得棘手。如何在保持视频画质的前提下&#xff0c;轻松减小视频文件大小&#xff1f;今天&#xff0c;就让我们一起探索苹果电脑上的几种高效视频压缩…

HP Superdome2小型机监控指标解读

监控易是一款专注于IT基础设施监控的软件&#xff0c;能够实时监控服务器的各项性能指标&#xff0c;确保服务器的稳定运行。针对HP Superdome2小型机&#xff0c;监控易通过IPMI和网页抓取数据的方式&#xff0c;监测包括服务器温度、风扇及电压等在内的关键指标&#xff0c;为…

Postfix+Dovecot+Roundcube开源邮件系统搭建系列1-2:系统搭建目标+MariaDB数据库配置(MySQL)

1. 系统搭建目标 通过本系列文章&#xff0c;最终可以部署一套提供如下服务的邮件系统&#xff1a; SMTP服务&#xff1a;由Postfix提供&#xff0c;监听25、465、587端口。POP3服务&#xff1a;由Dovecot提供&#xff0c;监听110、995端口。IMAP服务&#xff1a;由Dovecot提…

Java基础笔记(面试题)

一、Tomcat中为什么要使用自定义类加载器 Tomcat中可以放多个Java项目的jar文件&#xff0c;如果每个jar文件中都有一个User的类&#xff0c;那么User类在没有自定义类加载器的情况下是只能加载一次&#xff1b;想要加载多次&#xff0c;只能自定义类加载器 二、JDK、JRE、JVM…

项目管理进阶之RACI矩阵

前言 项目管理进阶系列续新篇。 RACI&#xff1f;这个是什么矩阵&#xff0c;有什么用途&#xff1f; 在项目管理过程中&#xff0c;如Team规模超5以上时&#xff0c;则有必要采用科学的管理方式&#xff0c;满足工作需要。否则可能事倍功半。 Q&#xff1a;什么是RACI矩阵 …

【简单介绍Gitea】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…