小秦哥 发表于 2024-7-16 12:09:34

WPF/C#:在WPF中怎样实现依靠注入

前言

本文通过 WPF Gallery 这个项目学习依靠注入的相关概念与怎样在WPF中进行依靠注入。
什么是依靠注入

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

[*]依靠:一个对象必要另一个对象来完成其工作,那么前者就依靠于后者。比方,一个OrderService类可能依靠于一个ProductRepository类来获取产物信息。
[*]注入:将依靠的对象传递给必要它的对象,而不是让必要它的对象自己去创建依靠的对象。注入可以通过构造函数、属性或方法参数来实现。
[*]容器:一个管理对象创建和依靠关系的框架或库。容器负责实例化对象,解析依靠关系,并将依靠的对象注入到必要它们的对象中。
依靠注入的范例
构造函数注入:依靠的对象通过类的构造函数传递。
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)是一种计划模式,通过它可以将对象的创建和对象之间的依靠关系的管理从对象内部转移到外部容器或框架中。进行依靠注入有以下几个紧张的原因和长处:

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

本文通过 WPF Gallery 项目学习在WPF中怎样使用依靠注入,代码地址:
https://github.com/microsoft/WPF-Samples/blob/main/SampleApplications/WPFGallery
这个项目中实现依靠注入,使用到了这两个包:
https://img-blog.csdnimg.cn/img_convert/ea39c63c8d3c751796ca18b621ddaaaa.png
首先查看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();


   
    public static void Main()
    {
      _host.Start();

      App app = new();
      app.InitializeComponent();
      app.MainWindow = _host.Services.GetRequiredService<MainWindow>();
      app.MainWindow.Visibility = Visibility.Visible;
      app.Run();
    }
}
https://img-blog.csdnimg.cn/img_convert/92395c06338ba54d0321a7abef7dfb8d.png
IHost是什么?
在C#中,IHost 是一个接口,它是.NET 中用于构建和配置应用程序的Host的概念的抽象。IHost接口界说了启动、运行和管理应用程序所需的服务和组件的集合。它通常用于ASP.NET Core应用程序,但也适用于其他范例的.NET 应用程序,如控制台应用程序或WPF程序。
https://img-blog.csdnimg.cn/img_convert/9edc5bda9a954a2c354e09bdaf2d6793.png
IHost接口由HostBuilder类实现,它提供了创建和配置IHost实例的方法。HostBuilder允许你添加各种服务,如日志记载、配置、依靠注入容器等,并配置应用程序的启动和停止活动。
https://img-blog.csdnimg.cn/img_convert/229f9ee1d671a257f35b41b09d5f1e63.png
https://img-blog.csdnimg.cn/img_convert/bebf5c8600a8ddd13ecb4bea12405eee.png
提供了用于使用预配置默认值创建Microsoft.Extensions.Hosting.IHostBuilder实例的方便方法。
https://img-blog.csdnimg.cn/img_convert/8f23f7136d1096f52221df84f84292fc.png
返回一个IHostBuilder。
https://img-blog.csdnimg.cn/img_convert/194f93f7669ee84ceda3b8008c34c2f0.png
https://img-blog.csdnimg.cn/img_convert/a0067ea0360451a0f858a7cbb5882f57.png
向容器中添加服务。此操纵可以调用多次,其效果是累加的。
参数configureDelegate的含义是配置Microsoft.Extensions.DependencyInjection.IServiceCollection的委托,
该集合将用于构造System.IServiceProvider。
该委托必要两个参数范例分别为HostBuilderContext、IServiceCollection没有返回值。
https://img-blog.csdnimg.cn/img_convert/ff5881416458b101aef08a0f517f2197.png
这里传入了一个满足该委托范例的Lambda表达式。
在C#中,() => {}是一种Lambda表达式的语法。Lambda表达式是一种轻量级的委托包装器,它可以让你界说一个匿名方法,并将其作为参数传递给支持委托或表达式树的方法。
Lambda表达式提供了一种简洁的方式来界说方法,特别是在必要将方法作为参数传递给其他方法时,它们非常有用。
https://img-blog.csdnimg.cn/img_convert/4c00b527176b35906840c2b4ac984725.png
在添加服务,这里出现了两种生命周期,除了AddSingleton、AddTransient外尚有AddScoped。
这些方法界说了服务的生命周期,即服务实例在应用程序中的创建和管理方式。
AddSingleton


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


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


[*]生命周期:作用域(Scoped)
[*]含义:在每个作用域内,服务实例是唯一的。作用域通常与请求的生命周期相关联,比方在Web应用程序中,每个HTTP请求会创建一个新的作用域。
[*]适用场景:适用于必要在请求范围内共享实例的服务,如数据库上下文。
使用这些服务
在Main函数中:
https://img-blog.csdnimg.cn/img_convert/22dafc1aa3a7bd5446346aae1ec97ae7.png
启动_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、(https://github.com/microsoft/WPF-Samples/tree/main/Sample Applications/WPFGallery)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: WPF/C#:在WPF中怎样实现依靠注入