ASP.NET Coreなどで今では当たり前のように使われるDIの仕組みですが、実際のところどうやって実装されているのかよく分かっていなかったので自前で実装して理解を深めようと思います。
目指すもの
次のようにDIコンテナへの型の登録とインスタンスの解決をできるところを目指します。
var container =new DiContainer(); // コンテナに適当なサービスを登録する container.Register<ICountdownService, CountdownService>(); container.Register<IDateTimeProvider, JstDateTimeProvider>(); // コンテナからインスタンスを取得するvar service = container.Resolve<IGreetService>(); service.CountdownToNewYear();
DIコンテナを実装する
インスタンスのライフタイムはシングルトンにしています。
publicclassDiContainer { privatereadonly Dictionary<Type, Type> _typeMappings =new(); privatereadonly Dictionary<Type, object> _instances =new(); publicvoid Register<TInterface, TImplementation>() { _typeMappings[typeof(TInterface)] =typeof(TImplementation); } publicT Resolve<T>() { return (T)Resolve(typeof(T)); } privateobject Resolve(Type type) { if (_instances.TryGetValue(type, outvar resolve)) { return resolve; } if (_typeMappings.TryGetValue(type, outvar implementationType)) { var constructor = implementationType.GetConstructors().First(); var parameterInfos = constructor.GetParameters(); // 目的のインスタンスのコンストラクタで必要なパラメータのインスタンスも再帰的に解決するvar parameterInstances = parameterInfos.Select(parameterInfo => Resolve(parameterInfo.ParameterType)); var implementation = Activator.CreateInstance(implementationType, parameterInstances.ToArray()); if (implementation isnotnull) { _instances[type] = implementation; return implementation; } } thrownew Exception($"Type {type.Name} not registered."); } }
var parameterInstances = parameterInfos.Select(parameterInfo => Resolve(parameterInfo.ParameterType));
のところで目的のインスタンスのコンストラクタで必要なパラメータのインスタンスも再帰的に解決しているところがミソな気がします。
DIの不思議だったところとして、コンストラクタのパラメータは誰がどうやって渡しているのか?が謎だったのですが、型(Type)から取得できるConstructorInfo、さらにそこから取得できるParameterInfoを使って必要なパラメータが何か分かるようになっているんですね。
実行結果
うまくいきました。
service.CountdownToNewYear();
// 新年まであと93日です