依存関係の挿入

ヒント

この内容は電子ブック『.NET MAUI を使用したエンタープライズ アプリケーション パターン』からの抜粋です。これは .NET Docs で閲覧することも、無料の PDF をダウンロードしてオフラインで読むこともできます。

電子ブック『.NET MAUI を使用したエンタープライズ アプリケーション パターン』の表紙のサムネイル。

通常、クラス コンストラクターは、オブジェクトをインスタンス化するときに呼び出され、オブジェクトに必要な値はすべて引数としてコンストラクターに渡されます。 これは、"コンストラクター挿入" と呼ばれる依存関係の挿入の一例です。 オブジェクトに必要な依存関係は、コンストラクターに挿入されます。

依存関係をインターフェイス型として指定すると、依存関係の挿入により、具象型を、これらの型に依存するコードから分離できます。 通常、インターフェイスと抽象型の登録とそれらの間のマッピングの一覧、およびこれらの型を実装または拡張する具象型を保持するコンテナーが使用されます。

依存関係の挿入には、他にも "プロパティ セッター挿入" や "メソッド呼び出し挿入" などの種類がありますが、あまり一般的ではありません。 このため、この章では、依存関係の挿入コンテナーを使用したコンストラクター挿入の実行にのみ焦点を当てます。

依存関係の挿入の概要

依存関係の挿入は、制御の反転 (IoC) パターンの特殊なバージョンであり、反転される懸念事項は、必要な依存関係を取得するプロセスです。 依存関係の挿入を使用すると、実行時に依存関係をオブジェクトに挿入する役割を別のクラスが担います。 次のコード例は、依存関係の挿入を使用する場合に ProfileViewModel クラスを構造化する方法を示しています。

private readonly ISettingsService _settingsService;
private readonly IAppEnvironmentService _appEnvironmentService;

public ProfileViewModel(
    IAppEnvironmentService appEnvironmentService,
    IDialogService dialogService, 
    INavigationService navigationService, 
    ISettingsService settingsService)
    : base(dialogService, navigationService, settingsService)
{
    _appEnvironmentService = appEnvironmentService;
    _settingsService = settingsService;

    // Omitted for brevity
}

ProfileViewModel コンストラクターは、複数のインターフェイス オブジェクト インスタンスを、別のクラスによって挿入された引数として受け取ります。 ProfileViewModel クラスの唯一の依存関係は、インターフェイス型に対するものです。 このため、ProfileViewModel クラスは、インターフェイス オブジェクトのインスタンス化を担当するクラスを認識しません。 インターフェイス オブジェクトをインスタンス化し、それを ProfileViewModel クラスに挿入する役割を担うクラスは、"依存関係の挿入コンテナー" と呼ばれます。

依存関係の挿入コンテナーにより、クラス インスタンスをインスタンス化し、コンテナーの構成に基づいてインスタンスの有効期間を管理するための機能が提供され、オブジェクト間の結合が削減されます。 オブジェクトの作成時に、コンテナーによって、オブジェクトに必要なすべての依存関係がオブジェクトに挿入されます。 これらの依存関係はまだ作成されていない場合、コンテナーでは最初に依存関係を作成して解決します。

依存関係の挿入コンテナーの使用には、いくつかの利点があります。

  • コンテナーを使用すると、クラスが依存関係を見つけてその有効期間を管理する必要がなくなります。
  • コンテナーを使用すると、クラスに影響を与えることなく、実装された依存関係をマッピングできます。
  • コンテナーを使用すると、依存関係をモックできるようになり、テストの容易性が向上します。
  • コンテナーを使用すると、新しいクラスをアプリに簡単に追加できるようになり、保守性が向上します。

MVVM を使用する .NET MAUI アプリのコンテキストでは、依存関係の挿入コンテナーは、通常、ビューの登録と解決、ビュー モデルの登録と解決、サービスの登録とビュー モデルへのそれらの挿入を行うために使用されます。

.NET では、多くの依存関係の挿入コンテナーを使用できます。eShop マルチプラットフォーム アプリでは、Microsoft.Extensions.DependencyInjection を使用して、アプリ内のビュー、ビュー モデル、サービス クラスのインスタンス化を管理します。 Microsoft.Extensions.DependencyInjection は、疎結合アプリの構築を容易にし、依存関係の挿入コンテナーで一般的に見られるすべての機能 (型マッピングとオブジェクト インスタンスの登録、オブジェクトの解決、オブジェクトの有効期間の管理、解決するオブジェクトのコンストラクターへの依存オブジェクトの挿入を行うメソッドなど) が含まれます。 Microsoft.Extensions.DependencyInjection の詳細については、「.NET での依存関係の挿入」を参照してください。

.NET MAUIでは、MauiProgram クラスで CreateMauiApp メソッドを呼び出して MauiAppBuilder オブジェクトを作成します。 MauiAppBuilder オブジェクトには、型 IServiceCollectionServices プロパティがあります。これは、依存関係の挿入のビュー、ビュー モデル、サービスなどのコンポーネントを登録する場所を提供します。 Services プロパティに登録されたコンポーネントは、MauiAppBuilder.Build メソッドが呼び出されると、依存関係の挿入コンテナーに提供されます。

実行時、コンテナーでは、要求されたオブジェクトに対してインスタンス化するために要求されているサービスの実装を認識する必要があります。 eShop マルチプラットフォーム アプリでは、ProfileViewModel オブジェクトをインスタンス化する前に、IAppEnvironmentServiceIDialogServiceINavigationServiceISettingsService インターフェイスを解決する必要があります。 このためには、次のアクションを実行するコンテナーが必要です。

  • インターフェイスを実装するオブジェクトのインスタンス化方法の決定。 これは "登録" と呼ばれます。
  • 必要なインターフェイスと ProfileViewModel オブジェクトを実装するオブジェクトのインスタンス化。 これは "解決" と呼ばれます。

最終的に、アプリは ProfileViewModel オブジェクトの使用を終了するため、ガベージ コレクションで使用できるようになります。 この時点で、他のクラスが同じインスタンスを共有していない場合、ガベージ コレクターは、有効期間の短いインターフェイス実装を破棄する必要があります。

登録

依存関係をオブジェクトに挿入するには、最初に依存関係の型をコンテナーに登録する必要があります。 型を登録するには、コンテナーにインターフェイスと、インターフェイスを実装する具象型を渡す必要があります。

コードを使用して型とオブジェクトをコンテナーに登録するには、次の 2 つの方法があります。

  • 型またはマッピングをコンテナーに登録します。 これは、一時的な登録と呼ばれます。 必要に応じて、コンテナーにより、指定された型のインスタンスが構築されます。
  • コンテナー内の既存のオブジェクトをシングルトンとして登録します。 必要に応じて、コンテナーにより、既存のオブジェクトへの参照が返されます。

Note

依存関係の挿入コンテナーがいつでも適しているとは限りません。 依存関係の挿入によって複雑さが増し、要件が増えるため、小さなアプリには適さない、または役に立たない可能性があります。 クラスに依存関係がない場合、または他の型の依存関係ではない場合、それをコンテナーに格納しても意味がない可能性があります。 さらに、型に不可欠な単一の依存関係セットがあり、変更されることがない場合も、それをコンテナーに格納しても意味がない可能性があります。

依存関係の挿入を必要とする型の登録は、アプリ内の単一のメソッドで実行する必要があります。 クラス間で依存関係を確実に認識できるように、このメソッドは、アプリのライフサイクルの早い段階で呼び出す必要があります。 eShop マルチプラットフォーム アプリでは、MauiProgram.CreateMauiApp メソッドを使用してこれを実行します。 次のコード例は、eShop マルチプラットフォーム アプリが MauiProgram クラス内で CreateMauiApp を宣言する方法を示しています。

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            // Omitted for brevity            
            .RegisterAppServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();
}

MauiApp.CreateBuilder メソッドでは、依存関係の登録に使用できる MauiAppBuilder オブジェクトが作成されます。 eShop マルチプラットフォーム アプリの依存関係の多くは登録する必要があるため、整理された保守可能な登録ワークフローを提供できるように、拡張メソッド RegisterAppServicesRegisterViewModelsRegisterViews が作成されました。 RegisterViewModels メソッドのコードを次に示します。

public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();

    mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();

    return mauiAppBuilder;
}

このメソッドは MauiAppBuilder のインスタンスを受け取るため、Services プロパティを使用してビュー モデルを登録できます。 アプリケーションのニーズによっては、有効期間が異なるサービスを追加することが必要な場合があります。 次の表では、これらの異なる登録有効期間の選択が必要になる可能性のある場合について説明します。

メソッド 説明
AddSingleton<T> アプリケーションの有効期間中残されるオブジェクトの単一のインスタンスを作成します。
AddTransient<T> 解決時に要求されたときに、オブジェクトの新しいインスタンスを作成します。 一時的なオブジェクトの有効期間は事前に定義されませんが、通常、ホストの有効期間に従います。

Note

ビュー モデルはインターフェイスから継承しないため、AddSingleton<T> および AddTransient<T> メソッドに指定された具象型のみが必要です。

CatalogViewModel はアプリケーションのルートの近くで使用され、常に使用できる必要があるため、AddSingleton<T> に登録すると便利です。 CheckoutViewModelOrderDetailViewModel などの他のビュー モデルは、状況に応じてアプリケーションに移動されるか、アプリケーションで後で使用されます。 常に使用されるとは限らないコンポーネントがあることがわかっているとします。 その場合、コンポーネントが、メモリ使用量または計算量が多いか、Just-In-Time データを必要とするのであれば、AddTransient<T> 登録の候補として適している可能性があります。

サービスを追加するもう 1 つの一般的な方法は、AddSingleton<TService, TImplementation> メソッドと AddTransient<TService, TImplementation> メソッドを使用することです。 これらのメソッドは、インターフェイス定義と具象実装の 2 つの入力の種類を受け取ります。 この種類の登録は、インターフェイスに基づいてサービスを実装する場合に最適です。 次のコード例では、SettingsService 実装を使用して ISettingsService インターフェイスを登録します。

public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
    // Omitted for brevity...
}

すべてのサービスが登録されたら、MauiAppBuilder.Build メソッドを呼び出して MauiApp を作成し、登録されたすべてのサービスを依存関係の挿入コンテナーに格納する必要があります。

重要

Build メソッドが呼び出されたら、依存関係の挿入コンテナーは不変になり、更新または変更できなくなります。 Build を呼び出す前に、アプリケーション内で必要なサービスすべてが登録されていることを確認してください。

解決方法

型は、登録された後、依存関係として解決または挿入できます。 型が解決され、コンテナーで新しいインスタンスを作成する必要がある場合、そのインスタンスに依存関係が挿入されます。

通常、型が解決されると、次の 3 つのいずれかが発生します。

  1. 型が登録されていない場合、コンテナーによって例外がスローされます。
  2. 型がシングルトンとして登録されていない場合、コンテナーによってシングルトン インスタンスが返されます。 型が呼び出されるのがこれが初めてである場合、コンテナーでは必要に応じてそれを作成し、それへの参照を保持します。
  3. 型が一時的として登録されている場合、コンテナーによって新しいインスタンスが返され、それに対する参照は保持されません。

.NET MAUI には、ニーズに基づいて登録済みコンポーネントを解決するための方法が数多く用意されています。 依存関係の挿入コンテナーにアクセスする最も直接的な方法は、Handler.MauiContext.Services を使用して Element からアクセスすることです。 その例を以下に示します。

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

これは、Element 内または Element のコンストラクターの外部からサービスを解決する必要がある場合に役立ちます。

注意

ElementHandler プロパティは null である可能性があるため、そのような状況に対処することが必要な場合があることに注意してください。 詳細については、Microsoft ドキュメント センターの「ハンドラーのライフサイクル」を参照してください。

.NET MAUI の Shell コントロールを使用している場合、ナビゲーション中に依存関係の挿入コンテナーが暗黙的に呼び出され、オブジェクトが作成されます。 Shell コントロールを設定すると、次の例に示すように、Routing.RegisterRoute メソッドでは、ルート パスを View に結び付けます。

Routing.RegisterRoute("Filter", typeof(FiltersView));

Shell のナビゲーション中、FiltersView の登録を探し、見つかった場合は、そのビューが作成され、依存関係がコンストラクターに挿入されます。 次のコード例に示すように、CatalogViewModelFiltersView に挿入されます。

namespace eShop.Views;

public partial class FiltersView : ContentPage
{
    public FiltersView(CatalogViewModel viewModel)
    {
        BindingContext = viewModel;

        InitializeComponent();
    }
}

ヒント

依存関係の挿入コンテナーは、ビュー モデル インスタンスを作成する場合に最適です。 ビュー モデルに依存関係がある場合、必要なサービスの作成と挿入が処理されます。 MauiProgram クラスの CreateMauiApp メソッドを使用して、ビュー モデルと、含まれる可能性があるすべての依存関係を必ず登録してください。

まとめ

依存関係の挿入により、具象型を、これらの型に依存するコードから分離できます。 通常、インターフェイスと抽象型の登録とそれらの間のマッピングの一覧、およびこれらの型を実装または拡張する具象型を保持するコンテナーが使用されます。

Microsoft.Extensions.DependencyInjection は、疎結合アプリの構築を容易にし、依存関係の挿入コンテナーで一般的に見られるすべての機能 (型マッピングとオブジェクト インスタンスの登録、オブジェクトの解決、オブジェクトの有効期間の管理、解決するオブジェクトのコンストラクターへの依存オブジェクトの挿入を行うメソッドなど) が含まれます。