首页 > 编程语言 >WPF/C#:在WPF中如何实现依赖注入

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

时间:2024-07-11 11:02:20浏览次数:20  
标签:依赖 C# 对象 services AddTransient 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)

标签:依赖,C#,对象,services,AddTransient,WPF,注入
From: https://blog.csdn.net/mingupup/article/details/140345877

相关文章

  • StarRocks跨集群迁移最佳实践|得物技术
    一、引言2024年之前,DBA维护的StarRocks集群存在在用低版本多、稳定性受组件bug影响大的问题,给日常运维带来一定压力,版本升级迫在眉睫。于是,我们在今年年初安排了针对2.5以下版本升级2.5.13的专项。这里和大家分享下,针对因版本兼容问题而不能原地升级的场景下,进行跨集群升级......
  • TCP协议三次握手和四次挥手原理图文解析
    前言TCP协议(TransmissionControlProtocol)是计算机网络中最常用的传输层协议之一,负责提供可靠、面向连接的数据传输服务。它存在的目的就是为了让传输更可靠,也更稳定,但同样也会对端口与端口之间的传输速率造成影响。它一般采用两种方式来使传输更加可靠。一种是面向连接,而另......
  • WPF 动态加载嵌入主程序的DLL
    WPF动态加载嵌入主程序的DLL,好处是节省文件数量,坏处是启动影响加载速度。首先将DLL添加进项目,选择添加现有项,设置生成操作为“嵌入资源”。代码:publicApp(){AppDomain.CurrentDomain.AssemblyResolve+=CurrentDomain_AssemblyResolve;......
  • Kubernetes高可用集群二进制离线部署(Runtime Docker)
    Kubernetes高可用集群二进制部署(RuntimeDocker)Kubernetes(简称为:k8s)是Google在2014年6月开源的一个容器集群管理系统,使用Go语言开发,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效,Kubernetes提供了资源调度、部署管理、服务......
  • Qt开发 | Qt创建线程 | Qt并发-QtConcurrent
    文章目录一、Qt创建线程的三种方法二、Qt并发:QtConcurrent介绍三、QtConcurrentrun参数说明四、获取QtConcurrent的返回值五、C++其他线程技术介绍一、Qt创建线程的三种方法  以下是Qt创建线程的三种方法:方法一:派生于QThread派生于QThread,这是Qt创建线程最常用......
  • ES6 Reflect 详解(三)
    Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有4个。将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只......
  • Android 应用、驱动开发(五十九)Calendar应用(DatePicker、TimePicker )
    一、运行效果:二、主函数MainActivity.java:packagecom.example.a074_calendarapp;importstaticjava.lang.String.format;importandroidx.appcompat.app.AppCompatActivity;importandroid.os.Bundle;importandroid.view.View;importandroid.widget.Button;im......
  • (免费领取源码)计算机毕业设计项目:宠物店管理系统 19849(开题答辩+程序定制+全套文案 )上
    目 录摘要1绪论1.1背景及意义1.2研究现状1.3springboot框架介绍2 宠物店管理系统系统分析2.1可行性分析2.2系统流程分析2.2.1数据流程3.3.2业务流程2.3系统功能分析2.3.1功能性分析2.3.2非功能性分析2.4系统用例分析2.5本章小结......
  • CodeForces - 1986G1 & CodeForces - 1986G2
    经过基本观察,可得当点对\((i,j)\)合法时,有\(a_i|b_j,a_j|b_i\),其中\(a_i=i/gcd(p_i,i),b_i=p_i/gcd(p_i,i)\),证明显然。如何维护?考虑开\(mp_{x,y}\)表示\(x=a_i\),\(y|b_i\)的个数。对于点\(i\)点对个数即为\(\sum\limits_{d|b_i}mp_{d,a_i}\)时间复杂度为\(O(nlog......
  • AquaCrop模型农业水资源管理及代码解析技术教程
    原文链接:AquaCrop模型农业水资源管理及代码解析技术教程https://mp.weixin.qq.com/s?__biz=MzUzNTczMDMxMg==&mid=2247608744&idx=5&sn=5b642a0f5a95138ae63b3edb9ec9a4b4&chksm=fa82684fcdf5e1596252586aa2f000fce2dc113b8a19ba88d4dd28f526071652918e32218f7e&token=55005666......