Обмен технологиями

WPF/C#: как реализовать внедрение зависимостей в WPF

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Предисловие

В этой статье используется проект галереи WPF, чтобы изучить соответствующие концепции внедрения зависимостей и способы выполнения внедрения зависимостей в WPF.

Что такое внедрение зависимостей

Внедрение зависимостей (DI) — это шаблон проектирования, используемый для реализации принципа инверсии управления (IoC). Основная цель внедрения зависимостей — перенести создание объектов и управление зависимостями между объектами изнутри объекта во внешний контейнер или фреймворк, тем самым улучшая сопровождаемость, тестируемость и гибкость кода.

Основная концепция внедрения зависимостей

  1. полагаться : объекту для завершения своей работы требуется другой объект, тогда первый зависит от второго. Например, класс OrderService может использовать класс ProductRepository для получения информации о продукте.
  2. инъекция : передать зависимый объект объекту, которому он нужен, вместо того, чтобы позволить объекту, которому он нужен, самому создать зависимый объект. Внедрение может быть достигнуто через конструкторы, свойства или параметры метода.
  3. контейнер : Платформа или библиотека, которая управляет созданием объектов и зависимостями. Контейнер отвечает за создание экземпляров объектов, разрешение зависимостей и внедрение зависимых объектов в объекты, которые в них нуждаются.

Типы внедрения зависимостей

внедрение конструктора: Зависимый объект передается через конструктор класса.

public class OrderService
{
    private readonly IProductRepository _productRepository;

    public OrderService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Внедрение свойств: Зависимые объекты передаются через общедоступные свойства класса.

public class OrderService
{
    public IProductRepository ProductRepository { get; set; }
}
  • 1
  • 2
  • 3
  • 4

метод инъекции: Зависимый объект передается через параметр метода класса.

public class OrderService
{
    public void ProcessOrder(IProductRepository productRepository)
    {
        // 使用 productRepository 处理订单
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Зачем необходимо внедрение зависимостей

Внедрение зависимостей (DI) — это шаблон проектирования, с помощью которого создание объектов и управление зависимостями между объектами можно перенести изнутри объекта во внешний контейнер или платформу. Существует несколько важных причин и преимуществ внедрения зависимостей:

  1. Уменьшите сцепление : Внедрение зависимостей передает управление зависимостями изнутри объекта во внешний контейнер, так что объекту не нужно знать, как создавать объекты, от которых он зависит, а нужно только знать интерфейс зависимого объекта. Это может значительно уменьшить связь между объектами и сделать код более модульным и гибким.
  2. Улучшение тестируемости : внедрение зависимостей делает модульное тестирование проще и эффективнее. Используя фиктивные объекты или заглушки вместо реальных зависимых объектов, разработчики могут выполнять модульное тестирование, не полагаясь на реальную реализацию. Это помогает обеспечить независимость и надежность теста.
  3. Улучшение ремонтопригодности : поскольку внедрение зависимостей уменьшает связь между объектами, код становится более модульным и понятным. Это упрощает понимание и поддержку кода. Когда вам нужно изменить или заменить зависимый объект, вам нужно изменить только конфигурационную или регистрационную информацию, не изменяя код, использующий этот объект.
  4. Повысить гибкость : внедрение зависимостей делает систему более гибкой и позволяет легко заменять зависимые объекты для достижения различных функций или поведения. Например, различные реализации уровня доступа к базе данных можно переключать через файлы конфигурации или код без изменения кода бизнес-логики.
  5. Содействие разделению интересов : Внедрение зависимостей помогает добиться разделения задач (Separation of Concerns), так что каждому объекту нужно сосредоточиться только на своих собственных обязанностях, не заботясь о том, как создавать и получать объекты, от которых он зависит. Это помогает улучшить ясность кода и удобство сопровождения.
  6. Поддержка шаблонов проектирования и лучших практик : Внедрение зависимостей лежит в основе многих шаблонов проектирования и передовых практик, таких как инверсия управления (IoC), шаблон локатора сервисов и т. д. Используя внедрение зависимостей, разработчики могут легче реализовывать эти шаблоны и методы, тем самым улучшая качество и масштабируемость кода.

Как реализовать внедрение зависимостей

В этой статье используется проект галереи WPF, чтобы узнать, как использовать внедрение зависимостей в адрес кода WPF:

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

Для реализации внедрения зависимостей в этом проекте используются эти два пакета:

изображение-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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

изображение-20240711083011393

Что такое IHost?

В С#,IHost это интерфейс, который используется в .NET для создания и настройки приложений.Hostпонятие абстрактное.IHost Интерфейс определяет набор служб и компонентов, необходимых для запуска, запуска приложения и управления им. Обычно он используется с приложениями ASP.NET Core, но также работает с другими типами приложений .NET, такими как консольные приложения или программы WPF.

изображение-20240711082817268

IHostИнтерфейс состоит изHostBuilderРеализация класса, обеспечивающая создание и настройкуIHostметоды экземпляра.HostBuilderПозволяет добавлять различные службы, такие как ведение журнала, настройка, контейнеры внедрения зависимостей и т. д., а также настраивать поведение при запуске и остановке вашего приложения.

изображение-20240711083048854

изображение-20240711083156306

Предоставляет удобные методы для создания экземпляров Microsoft.Extensions.Hosting.IHostBuilder с предварительно настроенными значениями по умолчанию.

изображение-20240711083713204

Возвращает IHostBuilder.

изображение-20240711084145756

изображение-20240711084211035

Добавьте сервисы в контейнер. Эту операцию можно вызывать несколько раз, и ее результаты суммируются.

Смысл параметра configureDelegate заключается в настройке делегата Microsoft.Extensions.DependencyInjection.IServiceCollection.
Эта коллекция будет использоваться для создания System.IServiceProvider.

Делегату требуются два типа параметров: HostBuilderContext и IServiceCollection, которые не имеют возвращаемого значения.

изображение-20240711084853971

Здесь передается лямбда-выражение, соответствующее типу делегата.

В С#,() => {} Это синтаксис лямбда-выражений. Лямбда-выражения — это облегченная оболочка делегата, которая позволяет вам определить анонимный метод и передать его в качестве параметра методу, поддерживающему делегаты или деревья выражений.

Лямбда-выражения предоставляют краткий способ определения методов, и они особенно полезны, когда вам нужно передавать методы в качестве параметров другим методам.

изображение-20240711085344696

При добавлении сервисов здесь есть два жизненных цикла, кроме AddSingleton и AddTransient, есть еще AddScoped.

Эти методы определяют жизненный цикл службы, то есть то, как экземпляры службы создаются и управляются в приложении.

ДобавитьСинглтон

  • жизненный цикл:Синглтон
  • значение : за весь жизненный цикл приложения создается только один экземпляр службы. Независимо от того, сколько раз вы запрашиваете его из контейнера, будет возвращен один и тот же экземпляр.
  • Применимая сцена: подходит для служб без отслеживания состояния или ресурсов, общих для всего приложения, таких как конфигурации, средства ведения журнала и т. д.

ДобавитьПереходный

  • жизненный цикл: Переходный
  • значение: каждый раз, когда служба запрашивается из контейнера, создается новый экземпляр.
  • Применимая сцена: подходит для сервисов с отслеживанием состояния или сценариев, требующих нового экземпляра для каждого запроса, например страниц, моделей представления и т. д.

AddScoped

  • жизненный цикл: Ограничено
  • значение : в каждой области экземпляры службы уникальны. Области часто связаны с жизненным циклом запроса, например, в веб-приложениях новая область создается с каждым HTTP-запросом.
  • Применимая сцена: подходит для служб, которым необходимо совместно использовать экземпляры в области запроса, например контексты базы данных.

используйте эти услуги

В основной функции:

изображение-20240711095100016

запускать_host,проходить_host.Services.GetRequiredService<MainWindow>();Получите экземпляр MainWindow.

Взяв в качестве примера класс MainWindow, просмотрите конструктор MainWindow в MainWindow.xaml.cs:

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

После удаления не относящегося к данной теме контента, получается следующее:

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
    _serviceProvider = serviceProvider;
    ViewModel = viewModel; 
    _navigationService = navigationService;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Вы заметили, что вам больше не нужно создавать новые эти объекты самостоятельно? Созданием этих объектов управляет контейнер внедрения зависимостей. Когда эти объекты необходимы, их можно внедрить через конструктор, как сейчас.

Если внедрение зависимостей не используется, это может выглядеть так:

public MainWindow()
{
    _serviceProvider = new IServiceProvider();
    ViewModel = new MainWindowViewModel(); 
    _navigationService = new INavigationService();  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Подведем итог

В этой статье сначала представлена ​​концепция внедрения зависимостей, затем объясняется, почему необходимо внедрение зависимостей, и, наконец, рассказывается, как использовать внедрение зависимостей в WPF с помощью проекта галереи WPF.

Ссылаться на

1、[WPF-Samples/Samples Applications/WPFGallery на главной · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Samples/WPFGallery)