きっかけ
最近はクリーンアーキテクチャやら何やら意識し始めたこともありDIコンテナの導入を積極的に行っている。
基本シーンファイルもあまり弄りたくないという思いもありLifetimeScopeもコードファーストで生成してみようと思った矢先の出来事。
VContainerはIDsposableを継承したクラスは自動的にDisposeメソッドをコールしてくれるはずなのだが、何故か呼ばれない。ということでVContainerの中身を読み漁るなどして解決してみた。
コード全文
using UnityEngine;
public class Test : MonoBehaviour
{
private TestScope mTestScope = null;
private void Start()
{
mTestScope = new TestScope();
}
private void OnDestroy()
{
mTestScope.Dispose();
}
}
using System;
using UnityEngine;
using VContainer;
using VContainer.Unity;
public class TestScope : IDisposable
{
private LifetimeScope mLifetimeScope = null;
public TestScope()
{
mLifetimeScope = LifetimeScope.Create(builder =>
{
builder.Register<Service>(Lifetime.Singleton);
builder.RegisterBuildCallback(container =>
{
var service = container.Resolve<Service>();
});
});
mLifetimeScope.Build();
}
public void Dispose()
{
Debug.Log($"LifetimeScope Disposed");
mLifetimeScope.Dispose();
}
}
public class Service : IDisposable
{
public void Dispose()
{
Debug.Log($"Service Disposed");
}
}
原因 / 解決方法
TestScope.csの以下の一文が原因
mLifetimeScope.Build();
元々LifetimeScopeを生成した時点でAutoRunがTrueとなっているため、2重に呼ばれていたのが原因だったよう。
そうと分かればBuildメソッドをコールしなければ良いだけなので簡単。
mLifetimeScope.Build();
の一行を消すか、AutoRunをfalseにすればいいだけ。
ただ、コードファーストでLifetimeScopeを生成した場合にはAutoRunをfalseにする前にBuildが走ってしまうきがする。そういったケースを再現する方法はちゃんと調べてないのでちょっと分からない。
要約すると以下のようになる。
- LifetimeScope.Createでインスタンス生成
- Registerで登録した型が生成候補リストに登録される
- AutoRunでLifetimeScopeのBuildが走る
- Registerで登録した型がコンテナに入り、生成候補のリストが空になる
- 自身で実行したBuildが再度走る
- 生成候補リストは空なのでdisposablesも当然呼ばれない!
といった流れ。
以上。
余談
builder.Register<IClass, Class>(Lifetime.SIngleton);
↑の構文でClassをResolveしようとするとエラーになるのがどういう実装でそうなっているのか気になったので調べてみたところ、↓の一文でInterfaceの登録が優先されていることが判明。だからAsSelf()使って実装したクラスもInterfaceとして登録しなきゃいけないという。
if (registration.InterfaceTypes is IReadOnlyList<Type> interfaceTypes)
さいごに
オープンソース最高。