Xamarin.Formsで依存性注入(Dependency Injection)


[Simple Injector](https://simpleinjector.org/index.html)というのを使ってXamarin.FormsでDIしてみる。

## DI化したいコード

今回はDIが目的なのでMVVMになっていないのは気にしない。

MainPage.xaml.cs や MainService.cs の中でインスタンスをnewしていて疎結合になっていないので、このままだとユニットテストしづらかったりする。


```dos:プロジェクト構成
DiTest
├─ Services
│　├─ MainService.cs
│　└─ SubService.cs
├─ Views
│　└─ MainPage.xaml
│　　　└─ MainPage.xaml.cs
└─ App.xaml
　　└─ App.xaml.cs
```

```c#:App.xaml.cs
using Xamarin.Forms;
using DiTest.Views;

namespace DiTest
{
	public partial class App : Application
	{
		public App() {
			InitializeComponent();
			MainPage = new MainPage();
		}
	}
}
```

```xml:MainPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
	xmlns="http://xamarin.com/schemas/2014/forms"
	xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
	x:Class="DiTest.Views.MainPage">

	<StackLayout VerticalOptions="Center" HorizontalOptions="Center">
		<Label x:Name="GreetLabel" Text="GreetLabel" />
	</StackLayout>

</ContentPage>
```

```c#:MainPage.xaml.cs
using Xamarin.Forms;
using DiTest.Services;

namespace DiTest.Views
{
	public partial class MainPage : ContentPage
	{
		public MainPage() {
			InitializeComponent();
			MainService service = new MainService();
			GreetLabel.Text = service.Greet();
		}
	}
}
```

```c#:MainService.cs
namespace DiTest.Services
{
	public class MainService
	{
		public MainService() {
		}

		public string Greet() {
			SubService subService = new SubService();
			return subService.Greet();
		}
	}
}
```

```c#:SubService.cs
namespace DiTest.Services
{
	public class SubService
	{
		public SubService() {
		}

		public string Greet() {
			return "Hello from SubService.";
		}
	}
}
```

## Simple Injectorを導入

PCLプロジェクトでメニューバーの[プロジェクト][NuGetパッケージの追加]から Simple Injector を追加する。

iOSプロジェクトにもパッケージを追加しなきゃダメっぽい。よくかわらん。

![nuget.png](https://qiita-image-store.s3.amazonaws.com/0/169798/ec1245d5-0d9b-fa72-3289-189a0e1dff73.png "nuget.png")


## コード修正

### Interfaceを追加

```dos:プロジェクト構成
DiTest
├─ Interfaces
│　├─ IMainService.cs <-- 追加
│　└─ ISubService.cs  <-- 追加
├─ Services
│　├─ MainService.cs
│　└─ SubService.cs
├─ Views
│　└─ MainPage.xaml
│　　　└─ MainPage.xaml.cs
└─ App.xaml
　　└─ App.xaml.cs
```

```c#:IMainService.cs
namespace DiTest.Interfaces
{
	public interface IMainService
	{
		string Greet();
	}
}
```

```c#:ISubService.cs
namespace DiTest.Interfaces
{
	public interface ISubService
	{
		string Greet();
	}
}
```

### Interfaceを実装

MainService のコンストラクタの引数に SubService のインターフェースを指定していることに注目。


```c#:MainService.cs
using DiTest.Interfaces;

namespace DiTest.Services
{
	public class MainService : IMainService
	{
		protected ISubService _subService;

		public MainService(ISubService subService) {
			_subService = subService;
		}

		public string Greet() {
			return _subService.Greet();
		}
	}
}
```

```c#:SubService.cs
using DiTest.Interfaces;

namespace DiTest.Services
{
	public class SubService : ISubService
	{
		public SubService() {
		}

		public string Greet()	{
			return "Hello from SubService.";
		}
	}
}
```

### 注入されてみる

```c#:MainPage.xaml.cs
using Xamarin.Forms;
using DiTest.Interfaces;

namespace DiTest.Views
{
	public partial class MainPage : ContentPage
	{
		public MainPage(IMainService mainService) { // コンストラクタの引数に注入するクラスのインターフェースを指定
			InitializeComponent();
			GreetLabel.Text = mainService.Greet();
		}
	}
}
```

```c#:App.xaml.cs
using Xamarin.Forms;
using DiTest.Views;
using DiTest.Interfaces;
using DiTest.Services;
using SimpleInjector;

namespace DiTest
{
	public partial class App : Application
	{
		public App() {
			InitializeComponent();

			// コンテナにサービスを登録
			Container container = new Container();
			container.Register<IMainService, MainService>();
			container.Register<ISubService, SubService>();

			// コンテナからインスタンス取り出し
			IMainService mainService = container.GetInstance<IMainService>();

			// インスタンスを注入
			MainPage = new MainPage(mainService);
		}
	}
}
```

### 結果

ちゃんと SubService の Greet メソッドが呼び出されている模様。

![di_result.png](https://qiita-image-store.s3.amazonaws.com/0/169798/a1e027a3-9b7b-a2a0-84a2-40c62173877b.png "di_result.png")


### だが、ちょっと待ってくれ...

MainService のコンストラクタに渡すはずのインスタンスを注入している箇所はどこにもないぞ？

### 自動的に注入される

コンテナに登録したサービスのコンストラクタに渡すインスタンスは、コンテナに登録してあれば自動で注入されるらしい。

Automatic constructor injection / auto-wiring
https://simpleinjector.readthedocs.io/en/latest/using.html#automatic-constructor-injection-auto-wiring

なるほどね。


## サービスを差し替えてみる

試しに、コンテナに登録するサービスを別のものに差し替えてみようと思う。

```dos:プロジェクト構成
DiTest
├─ Interfaces
│　├─ IMainService.cs
│　└─ ISubService.cs
├─ Services
│　├─ MainService.cs
│　├─ SubService.cs
│　└─ SubService2.cs <-- 追加
├─ Views
│　└─ MainPage.xaml
│　　　└─ MainPage.xaml.cs
└─ App.xaml
　　└─ App.xaml.cs
```

```c#:SubService2.cs
using DiTest.Interfaces;

namespace DiTest.Services
{
	public class SubService2 : ISubService
	{
		public string Greet() {
			return "Hello from SubService2.";
		}
	}
}
```

```c#:App.xaml.cs
using Xamarin.Forms;
using DiTest.Views;
using DiTest.Interfaces;
using DiTest.Services;
using SimpleInjector;

namespace DiTest
{
	public partial class App : Application
	{
		public App() {
			InitializeComponent();

			// コンテナにサービスを登録
			Container container = new Container();
			container.Register<IMainService, MainService>();
			container.Register<ISubService, SubService2>(); // SubService -> SubService2 に差し替え

			// コンテナからサービスインスタンス取り出し
			IMainService mainService = container.GetInstance<IMainService>();

			// インスタンスを注入
			MainPage = new MainPage(mainService);
		}
	}
}
```

### 結果

**SubService2** になっている。ちゃんと差し替わった模様。

![di_result2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/977916bf-5da4-f729-b1ca-a9774fe0c716.png "di_result2.png")



以上です。