aridai.NET
【WPF】DIコンテナのUnityを触ってみた

はじめに

今月からソフトウェア開発のお仕事をすることになり、業務寄りの知識を勉強しようと思ったので、DIコンテナの Unity を触ってみることにしました。

DIコンテナ自体は、最近書いているAndroidアプリの方で Dagger を触っているので、別に初めてではないのですが、WPFアプリケーションでは使ったことがないので、PrismXamlBehaviorsWpf の使い方のおさらいも兼ねて簡単なアプリを作ってみました。

リポジトリは こちら です。
このサンプルアプリの説明などは README.md を見ればある程度載っていますので、ここでは軽いメモ程度にとどめておこうと思います。

DIしている部分の軽い解説など

クラス図とコード

クラス図とコードは次のようになりました。
(本質的でない部分のコードは一部削っています。)

クラス図

/* Girlfriend.cs */

using System;

namespace UnityContainerApp.Models
{
    public abstract class Girlfriend
    {
        public abstract string Name { get; }
    }
}
/* Marisa.cs */

using System;

namespace UnityContainerApp.Models
{
    public sealed class Marisa : Girlfriend
    {
        public override string Name { get; } = "魔理沙";
    }
}
/* Alice.cs */

using System;

namespace UnityContainerApp.Models
{
    public sealed class Alice : Girlfriend
    {
        public override string Name { get; } = "アリス";
    }
}
/* Boyfriend.cs */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UnityContainerApp.Models
{
    public sealed class Boyfriend
    {
        public string Name { get; } = "aridai";
    }
}
/* Couple.cs */

using System;
using Unity;

namespace UnityContainerApp.Models
{
    public sealed class Couple
    {
        [Dependency]
        internal Boyfriend boyfriend;

        public Girlfriend Girlfriend { get; }

        public Boyfriend Boyfriend => this.boyfriend;

        [InjectionConstructor]
        public Couple([Dependency]Girlfriend girlfriend)
        {
            this.Girlfriend = girlfriend;
        }

        public override string ToString() => $"{this.Boyfriend.Name}{this.Girlfriend.Name}はカップルです。";
    }
}
/* MainWindowViewModel.cs */

using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Prism.Interactivity.InteractionRequest;
using Prism.Mvvm;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using Unity;
using UnityContainerApp.Models;

namespace UnityContainerApp.ViewModels
{
    public sealed class MainWindowViewModel : BindableBase
    {
        public string Text { get; }

        [InjectionConstructor]
        public MainWindowViewModel([Dependency]Couple couple)
        {
            this.Text = couple.ToString();
        }
    }
}
/* App.xaml.cs */

using System;
using System.Windows;
using Prism.Mvvm;
using Unity;
using UnityContainerApp.Models;
using UnityContainerApp.ViewModels;

namespace UnityContainerApp
{
    public partial class App : Application
    {
        private readonly IUnityContainer container = new UnityContainer();

        protected override void OnStartup(StartupEventArgs e)
        {
            #if DEBUG
                this.RegisterDependenciesForDebug();
            #else
                this.RegisterDependenciesForRelease();
            #endif

            ViewModelLocationProvider.SetDefaultViewModelFactory(type => this.container.Resolve(type));
        }

        private void RegisterDependenciesForRelease()
        {
            this.container.RegisterType<Girlfriend, Marisa>();
        }

        private void RegisterDependenciesForDebug()
        {
            this.container.RegisterType<Girlfriend, Alice>();
        }
    }
}

やっていること

見ての通り、彼女 (Girlfriend) を抽象化しています。
ReleaseビルドとDebugビルド・テストで、具象型の魔理沙 (Marisa) と アリス (Alice) の2つを差し替えるようにしています。
彼女の依存性の登録は this.container.RegisterType<Girlfriend, Marisa>(); の部分で行っています。
MarisaAlice も引数なしコンストラクタを持つ具象型ですので、DIコンテナが内部でインスタンスを生成でき、Girlfriend の型を解決するときに、それぞれのインスタンスを返してくれます。

もし、引数なしコンストラクタがない場合でも、コンストラクタの引数がDIコンテナが解決できる型のみで構成されているのであれば、手動で登録する必要はありません。
この例で言えば CoupleMainWindowViewModel がそうです。
どちらも、コンストラクタインジェクションをしているので、コンストラクタに引数を指定する必要があります。
しかし、Couple のコンストラクタは Girlfriend が、MainWindowViewModel のコンストラクタは Couple がDIコンテナが解決できる型なので、コードでは手動で登録処理を書いていません。
もし書くとするならば、次のようになるでしょう。

this.container.RegisterType<Girlfriend, Marisa>();
this.container.RegisterInstance<Boyfriend>(new Boyfriend());
this.container.RegisterInstance<Couple>(new Couple(this.container.Resolve<Girlfriend>()));
this.container.RegisterInstance<MainWindowViewModel>(new MainWindowViewModel(this.container.Resolve<Couple>()));
//  インスタンスを生成するのに必要なコンストラクタの引数を、DIコンテナが把握できているため、書かなくても勝手にやってくれる↑

また、Boyfriend はフィールドインジェクションで注入しています。
私の調査不足なのかもしれませんが、privateなsetterでもリフレクションで注入するといったことはやってくれないようです。
ですから、外部からアクセスできるアクセス修飾のsetterを持たせる必要があるみたいです。

作成日: 2019/05/05