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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apache Hadoop完全分布式集群搭建指南

Hadoop发行版本较多&#xff0c;Cloudera版本&#xff08;Cloudera’s Distribution Including Apache Hadoop&#xff0c;简称CDH&#xff09;收费版本通常用于生产环境&#xff0c;这里用开源免费的Apache Hadoop原始版本。 下载&#xff1a;Apache Hadoop 版本下载&#x…

华盈生物-PhenoCycler-超多靶标揭示组织空间位置和互作关系

华盈生物获得美国Akoya公司认证的PhenoCycler-Fusion&#xff08;原CODEX&#xff09;空间单细胞蛋白组技术服务商&#xff0c;并进入该技术的全球CRO服务提供者网络&#xff1a;CRO Service Providers | Akoya Biosciences为科研工作者提供为更具有精准医学特色的服务&#xf…

Failed to start mysql.service:Unit mysql.service not found(100%成功解决问题)

问题&#xff1a;在ubuntu中安装mysql后&#xff0c;启动mysql报错 你能看到我这篇文章&#xff0c;是非常幸运的&#xff01; 安装mysql-server: 然后启动mysql报错&#xff1a; 那要怎么处理呢&#xff1f;easy&#xff0c;跟着下面的步骤操作就好了 首先&#xff0c;先卸…

算法训练营day28--134. 加油站 +135. 分发糖果+860.柠檬水找零+406.根据身高重建队列

一、 134. 加油站 题目链接&#xff1a;https://leetcode.cn/problems/gas-station/ 文章讲解&#xff1a;https://programmercarl.com/0134.%E5%8A%A0%E6%B2%B9%E7%AB%99.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1jA411r7WX 1.1 初见思路 得模拟分析出…

php快速入门

前言 php是一门脚本语言&#xff0c;可以访问服务器&#xff0c;对数据库增删查改&#xff08;后台/后端语言&#xff09; 后台语言&#xff1a;php&#xff0c;java&#xff0c;c&#xff0c;c&#xff0c;python等等 注意&#xff1a;php是操作服务器&#xff0c;不能直接在…

教学神器大比拼:SmartEDA、Multisim、Proteus,谁是你的最佳选择?

随着科技的飞速发展&#xff0c;教学工具也在不断升级。在电子设计自动化&#xff08;EDA&#xff09;和电路仿真领域&#xff0c;SmartEDA、Multisim和Proteus三款软件备受关注。那么&#xff0c;对于广大教育工作者和学生们来说&#xff0c;这三者之间该如何选择呢&#xff1…

goaccess分析json格式日志

一.安装使用yum安装&#xff0c;yum install goaccess 二.主要介绍格式问题 1.nginx日志格式如下&#xff1a; log_format main escapejson {"time_local":"$time_local", "remote_addr":"$remote_addr", "r…

解决鸿蒙开发中克隆项目无法签名问题

文章目录 问题描述问题分析解决方案 问题描述 在一个风和日丽的早晨&#xff0c;这是我学习鸿蒙开发的第四天&#xff0c;把文档过了一遍的我准备看看别人的项目学习一下&#xff0c;于是就用git去clone了一个大佬的开源项目&#xff0c;在签名的时候遇到了问题&#xff1a; h…

docker 上传镜像到hub仓库

要将 Docker 镜像上传到 Docker Hub&#xff0c;你需要按照以下步骤操作&#xff1a; 登录 Docker Hub 首先&#xff0c;你需要登录到 Docker Hub。打开终端并运行以下命令&#xff1a;docker login系统会提示你输入 Docker Hub 的用户名和密码。 如果密码忘记可以token登录&a…

SAP S4 销售组的定义和分配

spro-企业结构-定义-销售与分销-维护销售组 新增一个记录 spro-企业结构-分配-销售与分销-给销售办公室分配销售组

[leetcode]kth-smallest-element-in-a-sorted-matrix 有序矩阵中第k小元素

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool check(vector<vector<int>>& matrix, int mid, int k, int n) {int i n - 1;int j 0;int num 0;while (i > 0 && j < n) {if (matrix[i][j] < mid) {num i 1;j;…

OTA与OTA升级

目录 一、OTA简介 二、OTA升级 三、操作方式 一、OTA简介 在嵌入式领域当中&#xff0c;OTA&#xff08;Over-The-Air&#xff09;指的是通过无线通信技术对嵌入式设备的软件进行远程更新和管理。这种技术广泛应用于物联网设备、智能家电、汽车电子、智能手机等领域。通过OTA…

自定义json序列化和反序列化

一、LocalDateTime反序列化异常 首先我们定义一个java POJO实体类&#xff0c;其中关键的成员变量时birthDate,我们没有采用Date数据类型&#xff0c;而是采用了Java8 新的日期类型LocalDateTime,使用LocalDateTime的好处我就不多说了&#xff0c;有很多的文章解释说明。我们把…

图鸟UI框架在uni-app多端应用开发中的实践与应用

摘要&#xff1a; 随着移动互联网的蓬勃发展&#xff0c;跨平台应用开发已成为行业趋势。本文将探讨图鸟UI框架如何在uni-app开发环境下助力开发者高效构建多端应用&#xff0c;并通过具体案例展示其在实际项目中的应用效果。 一、引言 在移动应用开发领域&#xff0c;跨平台…

高职软件测试实训室

一、高职软件测试实训室建设背景 随着《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》的深入实施&#xff0c;我国正在以不可阻挡的势头迈进数字化新时代。在这个波澜壮阔的时代背景下&#xff0c;软件作为数字经济的核心驱动力&#xff0c;其质量…

变量和常量(局部变量和全局变量)

常变的值叫变量&#xff0c;不变的值叫常量 变量分为局部变量和全局变量 在同一范围内&#xff0c;变量只能定义一次&#xff0c;否则就会报错 全部变量和局部变量是可以同时存在的&#xff0c;不过使用的时候是局部优先 变量如果你不给他初始化&#xff0c;那么他放得就是一…

【UML用户指南】-33-对体系结构建模-系统和模型

目录 1、系统和子系统 2、模型和视图 3、跟踪 4、常用建模技术 4.1、对系统的体系结构建模 4.2、对系统的系统建模 模型是对现实世界的简化——即对系统的抽象&#xff0c;建立模型的目的是为了更好地理解系统。 1、系统和子系统 一个系统可能被分解成一组子系统&#…

SAP ABAP ME21N 采购订单行项目屏幕增强

一、事务代码&#xff1a;SMOD 增强点&#xff1a;MM06E005 1.在CI_EKPODB 组建中添加自定义字段 2.事务码&#xff1a;SE11 进入CI_EKPODB 二、事务码&#xff1a;SE38 ZXM06TOP 定义结构 创建子屏幕 1.代码如下&#xff1a; TABLES:ci_ekpodb. DATA:EDIT_MODE TYPE cha…

Cocos如何跟iOS通信?

点击上方亿元程序员+关注和★星标 引言 Cocos如何跟iOS通信 大家好,相信小伙伴们通过阅读笔者前几期的文章**《你那么牛,怎么不教我打iOS包?安排!》,对Cocos如何打iOS**包有了一定的了解。 但是,除了把iOS包打出来,另外还有一个重要的就是要能够调用iOS提供的OC方法以…

2024数据挖掘实战

1、项目案例描述 沃尔玛全年都会举办几次促销减价活动。这些减价活动都是在重要节假日之前进行的&#xff0c;其中最大的四个节假日是超级碗、劳动节、感恩节和圣诞节。包括这些节假日在内的几周在评估中的权重是非节假日周的五倍。在缺乏完整/理想历史数据的情况下&#xff0…