Autofac について調べてみた その1 インスタンスのスコープで、不明だった点に関して師匠が答えてくれたので、私も検証してみた。
Instance Per Own のスコープの未解決事項
Instance Per Own 未解決事項 で書いた通り、実行結果が、普通のインジェクションにしても変わらなかったので、なんでこの書き方で書くのだろうと思ったのでそれを理解したかった。
using Autofac;
using Autofac.Features.OwnedInstances;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PerOwned
{
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<Backend>().AsImplementedInterfaces().InstancePerOwned<Command>();
builder.RegisterType<Command>();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope()) // using は 変数の Dispose() を呼ぶ
{
Console.WriteLine("BeginLifetimeScope");
using (var command = scope.Resolve<Owned<Command>>())
{
command.Value.Execute();
}
using (var command = scope.Resolve<Owned<Command>>())
{
command.Value.Execute();
}
Console.WriteLine("EndLifetimeScope");
}
Console.ReadLine();
}
}
interface IBackend
{
string Id { get; }
}
class Backend: IBackend, IDisposable
{
public string Id { get; } = Guid.NewGuid().ToString();
public void Dispose()
{
Console.WriteLine($"Backend#Dispose is called. InstanceId: {Id}");
}
}
class Command
{
private IBackend Backend { get; }
public Command(IBackend backend)
{
this.Backend = backend;
}
public void Execute()
{
Console.WriteLine($"Command#Execute is called with Backend {Backend.Id}");
}
}
}
ポイントはこちらだ。
builder.RegisterType<Backend>().AsImplementedInterfaces().InstancePerOwned<Command>();
builder.RegisterType<Command>();
新しい書き方になっているが、builder.RegisterType<Backend>().As<IBackend>() ...
と変わらない。直接指定しないのでこちらのほうがカッコいい。ポイントは、Command -> IBackend の依存性がある場合、Command#Dispose()
が呼ばれると、Backend#Dispose
も勝手に呼ばれるようになるのである。コードを見てわかるとおり、Command にはDispose()
のコードは書いていない。これを Owned
がラップしてくれている。
こちらの記事が詳しい。自分がわからなかったことも、師匠にかかれば一撃で解決してしまう。凄いものだ。
ちなみに、Owned を書かないやり方でもきっと同じことができるが面倒だ。例えば、 次のようなコードを書くとこの時点でエラーになる。何故かというと、using を使うためには、Dispose() が必要だが、Command に書いていないからだ。Command に Dispose() を書いてその中で、Backend の Dispose() を呼ぶようなコードを書いてあげればそれでいいと思うが、そういうことは、この書き方だとする必要がなくなる。
builder = new ContainerBuilder();
builder.RegisterType<Backend>().AsImplementedInterfaces();
builder.RegisterType<Command>();
container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
Console.WriteLine("BeginLifetimeScope without Owned class");
using (var command = scope.Resolve<Command>()) // ここでエラーになる
{
}
}
実行結果はこのとおり
BeginLifetimeScope
Command#Execute is called with Backend e84d604d-d902-4929-aa3b-72ad3b2a477c
Backend#Dispose is called. InstanceId: e84d604d-d902-4929-aa3b-72ad3b2a477c
Command#Execute is called with Backend 803b0570-fd63-4b63-96ed-bb2ec5e103f2
Backend#Dispose is called. InstanceId: 803b0570-fd63-4b63-96ed-bb2ec5e103f2
EndLifetimeScope
Command のライフサイクルで、Backend が作られて、Command が消されたら、Backend も消されている。