DI コンテナは複雑なライブラリです。それらを構築するには何年もかかり、それらを維持するには何十年もかかります。しかし、それらの動作を実証するために、わずか数行のコードで単純化された実装を作成できます。
DI コンテナは、通常System.Type
、キーとしてディクショナリをラップし、その値は、そのタイプの新しいインスタンスを作成できるオブジェクトになります。あなたが単純な実装を書くときはそうするSystem.Func<object>
でしょう。以下は、いくつかのメソッド (ジェネリック メソッドRegister
と非ジェネリック メソッドの両方)を含み、 Auto-WiringGetInstance
を許可する例です。
public class Container
{
private readonly Dictionary<Type, Func<object>> regs = new();
public void Register<TService, TImpl>() where TImpl : TService =>
regs.Add(typeof(TService), () => this.GetInstance(typeof(TImpl)));
public void Register<TService>(Func<TService> factory) =>
regs.Add(typeof(TService), () => factory());
public void RegisterInstance<TService>(TService instance) =>
regs.Add(typeof(TService), () => instance);
public void RegisterSingleton<TService>(Func<TService> factory)
{
var lazy = new Lazy<TService>(factory);
Register(() => lazy.Value);
}
public object GetInstance(Type type)
{
if (regs.TryGetValue(type, out Func<object> fac)) return fac();
else if (!type.IsAbstract) return this.CreateInstance(type);
throw new InvalidOperationException("No registration for " + type);
}
private object CreateInstance(Type implementationType)
{
var ctor = implementationType.GetConstructors().Single();
var paramTypes = ctor.GetParameters().Select(p => p.ParameterType);
var dependencies = paramTypes.Select(GetInstance).ToArray();
return Activator.CreateInstance(implementationType, dependencies);
}
}
次のように使用できます。
var container = new Container();
container.RegisterInstance<ILogger>(new FileLogger("c:\\logs\\log.txt"));
// SqlUserRepository depends on ILogger
container.Register<IUserRepository, SqlUserRepository>();
// HomeController depends on IUserRepository
// Concrete instances don't need to be resolved
container.GetInstance(typeof(HomeController));
警告:上記のような素朴で単純な実装は絶対に使用しないでください。成熟した DI ライブラリが提供する多くの重要な機能が欠けていますが、純粋な DI (つまり、オブジェクト グラフを手動で接続する)を使用するよりも利点はありません。何も返されずに、コンパイル時のサポートが失われます。
アプリケーションが小さい場合は、純粋な DI から開始する必要があります。アプリケーションと DI 構成が大きくなり、コンポジション ルートの維持が面倒になったら、確立された DI ライブラリの 1 つに切り替えることを検討できます。
確立されたライブラリと比較して、この単純な実装に欠けている機能の一部を次に示します。
- 自動登録:各タイプを手動で登録する代わりに、一連のタイプを 1 行で登録することにより、Convention over Configuration を適用する機能。
- インターセプト:さまざまなタイプのデコレーターまたはインターセプターを適用する機能
- ジェネリック:オープン ジェネリック抽象化をオープン ジェネリック実装にマッピングする
- 統合:一般的なアプリケーション プラットフォーム (ASP.NET MVC、Web API、.NET Core など) でライブラリを使用する
- ライフタイム管理:カスタム ライフスタイル (Scoped または Per Request など) を使用して型を登録する機能。
- エラー処理:循環依存などの構成ミスの検出。この単純な実装では、スタック オーバーフロー例外がスローされます。
- 検証:構成の正確性を検証し (コンパイル時のサポートの損失を補うため)、一般的な構成の誤りを診断するための機能またはツール。
- パフォーマンス:この単純な実装を使用すると、大きなオブジェクト グラフの構築が遅くなります (たとえば、大量のガベージが生成されるため、多くの GC プレッシャが発生します)。
これらの機能と機能により、DI コンテナーを使用する際に DI 構成を保守可能に保つことができます。