instanceCreator コンテキスト (aka ) を使用して単一の登録項目を登録できますFunc<T>
が、RegisterAll では同じ許可が得られないようです。
TL;DR - 受け入れられた回答を見つけて、更新 2 を確認してください (または、この質問の更新 3 までスキップしてください)。
これは私がやりたいことです:
container.RegisterAll<IFileWatcher>(
new List<Func<IFileWatcher>>
{
() => new FileWatcher(
@".\Triggers\TriggerWatch\SomeTrigger.txt",
container.GetInstance<IFileSystem>()),
() => new FileWatcher(
@".\Triggers\TriggerWatch\SomeOtherTrigger.txt",
container.GetInstance<IFileSystem>())
});
以前の Stack Overflow answer for multiple registrationsに基づいて拡張機能を追加しようとしましたが、最後のものが勝つようです:
public static class SimpleInjectorExtensions
{
public static void RegisterAll<TService>(this Container container,
IEnumerable<Func<TService>> instanceCreators)
where TService : class
{
foreach (var instanceCreator in instanceCreators)
{
container.RegisterSingle(typeof(TService),instanceCreator);
}
container.RegisterAll<TService>(typeof (TService));
}
}
そもそもなぜ存在する必要があるのか にも興味がありRegisterAll
ます。これは、私が使用した 5 つの依存性注入コンテナーのうち、違いを生む最初の依存性注入コンテナーです。Resolve<IEnumerable<TService>>
他のものは、サービスに対して複数のタイプを登録し、 (autofac) またはGetAllInstances<TService>
(SimpleInjector と Ninject の両方)を呼び出してそれらをすべてロードすることを可能にします。
アップデート
より明確にするために、個々のアイテムを処理するコンポジットに渡すことができるアイテムのリストを作成しようとしています。スケジュール、トリガー、およびイベント (Rx) に基づいて実行されるようにすべて登録されるタスクのグループに分類されるため、上記と同じ問題が発生します。レジスタをすべて削除して、他のものをいくつか取り除くには:
container.Register<ITask>(() => new FileWatchTask(
container.GetInstance<IFileSystem>(),
container.GetInstance<IMessageSubscriptionManagerService>(),
configuration,
container.GetAllInstances<IFileWatcher>()));
以前に登録されたファイル ウォッチャーのすべてのインスタンスを取得していることがわかります。
私が知る必要があるのは、この問題の簡単な回避策と、いつ実装されるか (実装されない場合は、実装されない理由) です。また、Simple Injector の設計の現在の制限を考慮すると、これが不可能であることも認めます。私が受け入れられないのは、ツールの制限を満たすためにアーキテクチャを変更および適応させる必要があるということです。
更新 2
OCP (Open Closed Principle、別名 SOLID の O) について話しましょう。また、SimpleInjector がこの特定の原則を場合によって破る方法について私が感じている印象について話しましょう。
Open Closed Principle とは、拡張に対してオープンであり、変更に対してクローズであるということです。これが意味することは、ソース コードを変更せずにエンティティの動作を変更できるということです。
ここで、ここに関連する例に移りましょう。
var tasks = container.GetAllInstances<ITask>();
foreach (var task in tasks.OrEmptyListIfNull())
{
//registers the task with the scheduler, Rx Event Messaging, or another trigger of some sort
task.Initialize();
}
それがどれほどきれいであるかに注意してください。ただし、これを行うには、インターフェイスのすべてのインスタンスを登録できる必要があります。
container.RegisterAll<ITask>(
new List<Func<ITask>>{
() => new FileWatchTask(container.GetInstance<IFileSystem>(),container.GetInstance<IMessageSubscriptionManagerService>(),configuration,container.GetAllInstances<IFileWatcher>()),
() => new DefaultFtpTask(container.GetInstance<IFtpClient>(),container.GetInstance<IFileSystem>()),
() => new DefaultImportFilesTask(container.GetInstance<IFileSystem>())
}
);
右?したがって、ここでの教訓は、これは優れており、OCP に適合しているということです。登録されているアイテムを追加または削除するだけで、タスク ランナーの動作を変更できます。拡張用に開き、変更用に閉じています。
次に、以下の回答で提案されている方法に焦点を当ててみましょう (最終的にこの質問に回答する 2 回目の更新の前)。これは、著者がより良いデザインであるという印象を与えています。
メンテナからの回答が言及している登録の優れた設計から始めましょう。私が得ている視点は、SimpleInjector で動作するように ITask をより柔軟にするために、コードを犠牲にしなければならないということです。
container.Register<ITask<SomeGeneric1>(() => new FileWatchTask(container.GetInstance<IFileSystem>(),container.GetInstance<IMessageSubscriptionManagerService>(),configuration,container.GetAllInstances<IFileWatcher>()));
container.Register<ITask<SomeGeneric2>(() => new DefaultFtpTask(container.GetInstance<IFtpClient>(),container.GetInstance<IFileSystem>()));
container.Register<ITask<SomeGeneric3>(() => new DefaultImportFilesTask(container.GetInstance<IFileSystem>()));
それでは、それによってデザインがどのように変化するかを見てみましょう。
var task1 = container.GetInstances<ITask<SomeGeneric1>();
task1.Initialize();
var task2 = container.GetInstances<ITask<SomeGeneric2>();
task2.Initialize();
var task3 = container.GetInstances<ITask<SomeGeneric3>();
task3.Initialize();
ああ。コンテナー登録にアイテムを追加または削除するたびに、コードの別のセクションも更新する必要があることがわかります。1 回の変更で 2 か所の変更、複数の設計上の問題を解決しています。
なぜコンテナにこれを要求しているのかと言うかもしれません。これはスタートアップ領域にありますが、そうでない場合は調べてみましょう。
したがって、コンストラクター注入を使用して、これがなぜ悪いのかを説明します。まず、私の例をコンストラクション インジェクションとして見てみましょう。
public class SomeClass {
public SomeClass(IEnumerable<ITask> tasks){}
}
素敵できれい。
さて、受け入れられた回答の見解についての私の理解に戻りましょう(更新2の前に):
public class SomeClass {
public SomeClass(ITask<Generic1> task1,
ITask<Generic2> task2,
ITask<Generic3> task3
) {}
}
ああ。コードの複数の領域を編集しなければならないたびに、この設計がどれほど貧弱であるかについては始めません。
ここでの教訓は何ですか?私は世界で最も賢い男ではありません。私は複数のフレームワークを維持しています (または維持しようとしています :))、他の人よりも多くのことを知っているふりをしたり、他の人よりもよく知っているふりをしたりしません。私のデザイン感覚がゆがんでいるのかもしれませんし、私がまだ考えもしていない未知の方法で他の人を制限しているのかもしれません. 著者がデザインに関するアドバイスをするのは善意であると確信していますが、場合によっては、特に私たちが何をしているのかを知っている私たちにとっては、迷惑な (そして少し見下すような) ものに遭遇するかもしれません.
アップデート 3
そのため、質問はメンテナーからの更新 2 で回答されました。私は RegisterAll を使用しようとしていました。これは、私が使用できるとは思いもよらなかったためですRegister<IEnumerable<T>>
(残念ながら、ドキュメントではこれが指摘されていませんでした)。今では完全に当たり前のように思えますが、他の IoC フレームワークから移行しようとしている人は荷物を抱えており、この驚くべきデザインの簡素化を見逃す可能性があります。他に 4 つの DI コンテナーを持っていたので、見逃してしまいました。うまくいけば、彼はそれをドキュメントに追加するか、もう少しうまく呼び出すでしょう.