技術共有

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. 懸念事項の分離を促進する : 依存関係の注入は、懸念の分離 (懸念の分離) を達成するのに役立ちます。そのため、各オブジェクトは、依存するオブジェクトの作成方法や取得方法を気にすることなく、自分自身の責任のみに集中する必要があります。これは、コードの明瞭さと保守性の向上に役立ちます。
  6. 設計パターンとベストプラクティスをサポート : 依存関係の注入は、制御の反転 (IoC)、サービス ロケーター パターンなど、多くの設計パターンとベスト プラクティスの基礎です。依存関係の注入を使用すると、開発者はこれらのパターンと実践をより簡単に実装できるため、コードの品質とスケーラビリティが向上します。

依存関係注入の実装方法

この記事では、WPF ギャラリー プロジェクトを使用して、WPF コード アドレスで依存関係の挿入を使用する方法を学習します。

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

このプロジェクトで依存関係の注入を実装するには、次の 2 つのパッケージが使用されます。

画像-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

Iホストとは何ですか?

C#では、IHost .NET でアプリケーションを構築および構成するために使用されるインターフェイスですHost概念の要約。IHostインターフェイスは、アプリケーションの起動、実行、管理に必要なサービスとコンポーネントのコレクションを定義します。通常、ASP.NET Core アプリケーションで使用されますが、コンソール アプリケーションや WPF プログラムなど、他の種類の .NET アプリケーションでも動作します。

画像-20240711082817268

IHostインターフェースは次のもので構成されますHostBuilderクラス実装。これにより、IHostインスタンスメソッド。HostBuilderロギング、構成、依存関係注入コンテナーなどのさまざまなサービスを追加し、アプリケーションの起動および停止の動作を構成できます。

画像-20240711083048854

画像-20240711083156306

事前構成された既定値を使用して Microsoft.Extensions.Hosting.IHostBuilder インスタンスを作成するための便利なメソッドを提供します。

画像-20240711083713204

IHostBuilder を返します。

画像-20240711084145756

画像-20240711084211035

コンテナにサービスを追加します。この操作は複数回呼び出すことができ、その結果は累積されます。

パラメーターconfigureDelegateの意味は、Microsoft.Extensions.DependencyInjection.IServiceCollectionのデリゲートを構成することです。
このコレクションは、System.IServiceProvider を構築するために使用されます。

デリゲートには、HostBuilderContext と IServiceCollection という 2 つのパラメーター タイプが必要ですが、これらには戻り値がありません。

画像-20240711084853971

デリゲート型を満たすラムダ式がここに渡されます。

C#では、() => {}ラムダ式の構文です。 ラムダ式は、匿名メソッドを定義し、それをデリゲートまたは式ツリーをサポートするメソッドにパラメータとして渡すことを可能にする軽量のデリゲート ラッパーです。

ラムダ式はメソッドを定義する簡潔な方法を提供し、メソッドをパラメータとして他のメソッドに渡す必要がある場合に特に役立ちます。

画像-20240711085344696

サービスを追加する場合、ここには 2 つのライフサイクルがあり、AddSingleton と AddTransient に加えて、AddScoped もあります。

これらのメソッドは、サービスのライフサイクル、つまりアプリケーション内でサービス インスタンスがどのように作成および管理されるかを定義します。

シングルトンの追加

  • ライフサイクル:シングルトン
  • 意味 : アプリケーションのライフサイクル全体を通じて、サービス インスタンスは 1 つだけ作成されます。コンテナから何度リクエストしても、同じインスタンスが返されます。
  • 該当シーン: ステートレス サービス、またはアプリケーション全体で共有されるリソース (構成、ロガーなど) に適しています。

一時的追加

  • ライフサイクル: トランジェント
  • 意味: コンテナからサービスが要求されるたびに、新しいインスタンスが作成されます。
  • 該当シーン: ステートフル サービス、またはページやビュー モデルなど、リクエストごとに新しいインスタンスを必要とするシナリオに適しています。

スコープを追加

  • ライフサイクル: スコープ付き
  • 意味 : 各スコープ内で、サービス インスタンスは一意です。スコープはリクエストのライフサイクルに関連付けられることがよくあります。たとえば、Web アプリケーションでは、HTTP リクエストごとに新しいスコープが作成されます。
  • 該当シーン: データベース コンテキストなど、リクエスト スコープ内でインスタンスを共有する必要があるサービスに適しています。

これらのサービスを利用する

Main 関数内:

画像-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;
}
  • 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/Sample Applications/WPFGallery at main · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Sample Applications/WPFGallery)