fer*_*der 7 .net c# dependency-injection ioc-container simple-injector
我可以使用instanceCreator上下文(aka Func<T>)注册一个注册项,但似乎与RegisterAll没有相同的允许.
这就是我想要做的:
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>())
});
Run Code Online (Sandbox Code Playgroud)
我尝试添加基于先前Stack Overflow答案的扩展,以进行多次注册,但似乎最后一个获胜:
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));
}
}
Run Code Online (Sandbox Code Playgroud)
我也很好奇为什么首先需要RegisterAll存在.这是我用过的第一个依赖注入容器中的第一个依赖注入容器.其他只允许您针对服务注册多个类型,然后通过调用Resolve<IEnumerable<TService>>(autofac)或GetAllInstances<TService>(SimpleInjector和Ninject)加载它们.
为了更清楚,我正在尝试构建一个项目列表,我可以将其传递给处理每个单独项目的组合.它遇到与上述相同的问题,因为它属于一组任务,所有任务都根据计划,触发器和事件(Rx)运行.暂时删除寄存器并删除其他一些东西:
container.Register<ITask>(() => new FileWatchTask(
container.GetInstance<IFileSystem>(),
container.GetInstance<IMessageSubscriptionManagerService>(),
configuration,
container.GetAllInstances<IFileWatcher>()));
Run Code Online (Sandbox Code Playgroud)
您可以看到我正在抓取以前注册的文件观察者的所有实例.
我需要知道的是这个问题的一个简单的解决方法以及它何时实现(如果没有,为什么它不会).我也会接受,鉴于Simple Injector设计的当前局限性,这是不可能的.我不接受的是我需要改变和调整我的架构以满足工具的局限性.
让我们来谈谈OCP(开放封闭原则,又称SOLID中的O)以及我在某些情况下如何简化SimpleInjector打破这一特定原则的印象.
开放封闭原则就是这样,开放进行扩展,但关闭进行修改.这意味着您可以在不更改其源代码的情况下更改实体的行为.
现在让我们转到一个与此相关的示例:
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();
}
Run Code Online (Sandbox Code Playgroud)
注意那是多么干净.为了能够做到这一点,我需要能够注册接口的所有实例:
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>())
}
);
Run Code Online (Sandbox Code Playgroud)
对?所以这里的教训是,这很好并且符合OCP.我只需添加或删除已注册的项目即可更改任务运行器的行为.打开扩展,关闭以进行修改.
现在让我们集中精力尝试按照下面的答案中建议的方式(在第二次更新之前,最终回答这个问题),作者给人的印象是更好的设计.
让我们从维护者提到的答案开始就是良好的注册设计.我得到的观点是,我必须牺牲我的代码,以某种方式使ITask更灵活地使用SimpleInjector:
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>()));
Run Code Online (Sandbox Code Playgroud)
现在让我们看看如何改变我们的设计:
var task1 = container.GetInstances<ITask<SomeGeneric1>();
task1.Initialize();
var task2 = container.GetInstances<ITask<SomeGeneric2>();
task2.Initialize();
var task3 = container.GetInstances<ITask<SomeGeneric3>();
task3.Initialize();
Run Code Online (Sandbox Code Playgroud)
哎哟.您可以看到每次我从容器注册中添加或删除项目时,我现在还需要更新另一部分代码.对于一次更改的两个修改地点,我打破了多个设计问题.
你可能会说我为什么要问这个容器?那么这是在创业区,但让我们探索一下我是不是.
所以我将使用构造函数注入来说明为什么这很糟糕.首先让我们看一下构造注入的例子.
public class SomeClass {
public SomeClass(IEnumerable<ITask> tasks){}
}
Run Code Online (Sandbox Code Playgroud)
干净整洁.
现在,让我们回到我对接受的答案视图的理解(再次在更新2之前):
public class SomeClass {
public SomeClass(ITask<Generic1> task1,
ITask<Generic2> task2,
ITask<Generic3> task3
) {}
}
Run Code Online (Sandbox Code Playgroud)
哎哟.每次我必须编辑多个代码区域时,让我们甚至不了解这个设计有多糟糕.
这是什么教训?我不是世界上最聪明的人.我维护(或尝试维护:))多个框架,我不会假装我知道比其他人更多或更好.我的设计感可能会有所偏差,或者我可能会以某种我未曾想过的未知方式限制他人.我确信作者在提出设计建议方面意味着很好,但在某些情况下,它可能会遇到烦人的(并且有点居高临下),特别是对于我们这些知道我们正在做什么的人.
所以维护者在更新2中回答了这个问题.我试图使用RegisterAll,因为我没有想到我可以使用它Register<IEnumerable<T>>(不幸的是文档没有指出这一点).现在看起来非常明显,但是当人们从其他IoC框架中跳出来时,他们带着一些包袱,并且可能会错过这种极好的设计简化!我错过了它,另外还有4个DI容器.希望他将它添加到文档中或者更好地调用它.
Ste*_*ven 16
从您的第一个示例(使用List<Func<IFileWatcher>>)我明白您想要注册一组瞬态文件监视器.换句话说,每次迭代列表时,都应该创建一个新的文件监视器实例.这当然与使用两个(单例)文件监视器(始终返回的相同实例)注册列表非常不同.但是你的问题有些含糊不清,因为在扩展方法中你似乎将它们注册为单例.对于我的其余部分,我会假设你想要暂时的行为.
创建的常见用例RegisterAll是注册公共接口的实现列表.例如,具有多个IEventHandler<CustomerMoved>实现的应用程序,当CustomerMoved事件被引发时都需要触发.在这种情况下,您为RegisterAll方法提供了System.Type实例列表,并且容器完全可以控制为您实现这些实现的连接.由于容器控制着创建,因此该集合称为"容器控制".
该RegisterAll然而,仅仅转发创建回容器,这意味着,在默认情况下在创建瞬态实例列表结果(因为未注册的具体类型被解析为瞬态).这看起来很尴尬,但它允许您注册具有不同生活方式元素的列表,因为您可以使用所选择的生活方式明确注册每个项目.它还允许您提供RegisterAll带抽象(例如typeof(IService)),这也将起作用,因为请求被转发回容器.
但是,您的用例不同.您希望注册完全相同类型的元素列表,但每个元素具有不同的配置值.为了使事情变得更加困难,你似乎想要将它们注册为瞬态而不是单身.通过不传递RegisterAll类型列表,但IEnumerable<TService>容器不创建并自动连接这些类型,我们称之为"容器不受控制"的集合.
长话短说:我们如何注册?有多种方法可以做到这一点,但我个人喜欢这种方法:
string[] triggers = new[]
{
@".\Triggers\TriggerWatch\SomeTrigger.txt",
@".\Triggers\TriggerWatch\SomeOtherTrigger.txt"
};
container.RegisterAll<IFileWatcher>(
from trigger in triggers
select new FileWatcher(trigger,
container.GetInstance<IFileSystem>())
);
Run Code Online (Sandbox Code Playgroud)
这里我们IEnumerable<T>使用该RegisterAll方法注册一个LINQ查询(只是一个).每当有人解析IEnumerable<IFileWatcher>它时,它返回相同的查询,但由于该查询的选择包含a new FileWatcher,在迭代时总是返回新实例.使用以下测试可以看到此效果:
var watchers = container.GetAllInstances<IFileWatcher>();
var first1 = watchers.First();
var first2 = watchers.First();
Assert.AreNotEqual(first1, first2, "Should be different instances");
Assert.AreEqual(first1.Trigger, first2.Trigger);
Run Code Online (Sandbox Code Playgroud)
正如此测试所示,我们一次解析集合,但每次迭代它(.First()迭代集合)时,都会创建一个新实例,但两个实例都具有相同的@".\Triggers\TriggerWatch\SomeTrigger.txt"值.
正如您所看到的,没有任何限制可以阻止您有效地执行此操作.但是,您可能需要以不同的方式思考.
我也很好奇为什么首先需要RegisterAll存在.
这是一个非常明确的设计决策.你是对的,大多数其他容器只允许你做一堆相同类型的注册,当被要求收集时,所有注册都会被返回.问题是很容易意外地再次注册一个类型,这是我想要防止的.
此外,所有容器都有不同的行为,在请求单个实例而不是请求收集时返回注册.有些人返回第一次注册其他人返回最后一次.我也希望防止这种歧义.
最后但同样重要的是,请注意,注册相同类型的项目集合通常应该是一个例外.根据我的经验,90%的时候,当开发人员想要注册多种类型的相同抽象时,他们的设计存在一些模糊性.通过明确注册集合,我希望让这个突出.
我不接受的是我需要改变和调整我的架构以满足某些工具的局限性.
我同意这一点.您的架构应该是领先的,而不是工具.你应该相应地选择你的工具.
但请注意,Simple Injector有许多限制,并且大多数限制都是故意选择的,以刺激用户进行简洁的设计.例如,每次违反代码中的SOLID原则之一时,您都会遇到问题.您将难以保持代码的灵活性,测试可读性以及组合根的可维护性.这实际上适用于所有DI容器,但对于Simple Injector来说可能更多.这是故意的,如果开发人员对应用SOLID原则不感兴趣并且想要一个在任何给定环境下都能工作的DI容器,那么Simple Injector可能不是最好的工具.例如,将Simple Injector应用于遗留代码库可能会令人生畏.
我希望这给出了Simple Injector设计的一些看法.
UPDATE
如果你需要单身,这甚至更简单.您可以按如下方式注册:
var fs = new RealFileSystem();
container.RegisterSingle<IFileSystem>(fs);
container.RegisterAll<IFileWatcher>(
new FileWatcher(@".\Triggers\TriggerWatch\SomeTrigger.txt", fs),
new FileWatcher(@".\Triggers\TriggerWatch\SomeOtherTrigger.txt", fs)
);
Run Code Online (Sandbox Code Playgroud)
更新2
您明确要求RegisterAll<T>(Func<T>)支持懒惰地创建集合.事实上已经有了对此的支持,只需使用RegisterSingle<IEnumerable<T>>(Func<IEnumerable<T>>),正如您在此处所见:
container.RegisterSingle<IEnumerable<IFileWatcher>>(() =>
{
return
from
var list = new List<IFileWatcher>
{
new FileWatcher(@".\Triggers\TriggerWatch\SomeTrigger.txt", container.GetInstance<IFileSystem>()),
new FileWatcher(@".\Triggers\TriggerWatch\SomeOtherTrigger.txt", container.GetInstance<IFileSystem>())
};
return list.AsReadOnly();
});
Run Code Online (Sandbox Code Playgroud)
RegisterAll<T>(IEnumerable<T>)实际上这是一个方便的重载,最终会调用RegisterSingle<IEnumerable<T>>(collection).
请注意,我显式返回一个只读列表.这是可选的,但它是一种额外的安全机制,可防止任何应用程序代码更改集合.使用RegisterAll<T>集合时会自动包装在只读迭代器中.
使用的唯一方法RegisterSingle<IEnumerable<T>>是在调用时容器不会迭代集合container.Verify().但是,在您的情况下,这不会是一个问题,因为当集合的一个元素未能初始化调用时GetInstance<IEnumerable<IFileWatcher>>也会失败并且调用它Verify().
更新3
如果我给人的印象是我的意思是你的设计错了,我道歉.我无从得知这一点.既然您明确询问了为什么某些功能缺失,我会尽力解释其背后的基本原理.然而,这并不意味着我认为你的设计很糟糕,因为我无法知道.
让我们回到维护者对优秀设计的看法
我不确定你为什么认为这是我对优秀设计的看法?有一个SomeClass与需要每次在系统中添加任务时改变构造绝对不是一个好的设计.我们可以安全地同意这一点.这打破了OCP.我永远不会建议任何人做这样的事情.除了具有许多参数的构造函数之外,至少是一种设计气味.Simple Injector的下一个次要版本甚至会添加一个关于具有太多依赖关系的类型的诊断警告,因为这通常表示SRP违规.但再次看看Simple Injector如何通过提供指导来试图"帮助"开发人员.
但是,我确实推广了通用接口的使用,并且这是Simple Injector设计特别优化的情况.ITask界面就是一个很好的例子.在这种情况下,ITask<T>通常会对您希望执行的某些业务行为进行抽象,并且T是一个参数对象,它包含要执行的操作的所有参数(您可以将其视为带有消息处理程序的消息).然而,这仅在消费者需要使用特定参数集(特定版本T)执行操作时才有用,例如它想要执行ITask<ShipOrder>.由于您在不提供参数的情况下执行一批所有任务,因此基于的设计ITask<T>可能会很尴尬.
但让我们假设它是合适的.让我们假设这一点,所以我可以解释在这种情况下如何优化Simple Injector.在本次更新结束时,我将向您展示Simple Injector如何在您的情况下仍能提供帮助,请屏住呼吸.在您的代码示例中,您按如下方式注册通用任务:
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>()));
Run Code Online (Sandbox Code Playgroud)
这是一种在系统中注册所有任务的相当痛苦的方法,因为每次更改任务实现的构造函数时,都必须更改此代码.Simple Injector允许您通过查看其构造函数来自动连接类型.换句话说,Simple Injector允许您将此代码简化为以下内容:
container.Register<ITask<SomeGeneric1>, FileWatchTask>();
container.Register<ITask<SomeGeneric2>, DefaultFtpTask>();
container.Register<ITask<SomeGeneric3>, DefaultImportFilesTask>();
Run Code Online (Sandbox Code Playgroud)
这已经更加可维护,可以带来更好的性能,并允许您稍后添加其他有趣的场景,例如基于上下文的注入(因为Simple Injector控制整个对象图).这是在Simple Injector中注册事物的建议方法(如果可能,阻止使用Func).
尽管如此,当拥有一个以任务为中心元素的架构时,您可能会定期添加新的任务实现.这将导致拥有数十个注册行,并且每次添加任务时都必须返回此代码以添加一行.然而,Simple Injector具有批量注册功能,允许您将其缩减回一行代码:
// using SimpleInjector.Extensions;
container.RegisterManyForOpenGeneric(typeof(ITask<>), typeof(ITask<>).Assembly);
Run Code Online (Sandbox Code Playgroud)
通过调用此行,容器将搜索ITask<T>位于接口程序集中的所有实现,并将为您注册它们.由于这是在运行时使用反射完成的,因此在将新任务添加到系统时不必更改该行.
而且由于你在谈论OCP,IMO Simple Injector对OCP有很大的支持.在某些方面,它甚至击败了所有其他框架.当我想到OCP时,我特别想到一个特定的模式:装饰模式.装饰器模式是应用OCP时非常重要的模式.例如,不应该通过更改某些业务逻辑本身来添加横切关注点,但最好通过使用装饰器包装类来添加.使用Simple Injector,可以使用一行代码添加装饰器:
// using SimpleInjector.Extensions;
container.RegisterDecorator(typeof(ITask<>), typeof(TransactionTaskDecorator<>));
Run Code Online (Sandbox Code Playgroud)
这确保了(瞬态)在解决后TransactionTaskDecorator<T>围绕所有ITask<T>实现.这些装饰器集成在容器的管道中,这意味着它们可以具有自己的依赖关系,可以具有初始化器,并且可以具有特定的生活方式.装饰器可以轻松堆叠:
container.RegisterDecorator(typeof(ITask<>), typeof(TransactionTaskDecorator<>));
container.RegisterDecorator(typeof(ITask<>), typeof(DeadlockRetryTaskDecorator<>));
Run Code Online (Sandbox Code Playgroud)
这将事务装饰器中的所有任务包装起来,并在死锁重试装饰器中再次包装该事务装饰器.您甚至可以有条件地应用装饰器:
container.RegisterDecorator(typeof(ITask<>), typeof(ValidationTaskDecorator<>),
context => ShouldApplyValidator(context.ServiceType));
Run Code Online (Sandbox Code Playgroud)
如果你的装饰器有一个泛型类型约束,Simple Injector会在泛型类型约束匹配时自动应用装饰器,你不需要做任何事情.而且,由于Simple Injector生成表达式树并将它们编译为代理,这都是一次性成本.这并不意味着它是免费的,但你只付一次,而不是每一个决心.
没有其他DI库可以使装饰器像Simple Injector一样简单灵活.
所以这就是Simple Injector真正闪耀的地方,但这对你没什么帮助:-).在这种情况下,通用接口对您没有帮助,但即使在您的情况下,您也可以使您的注册更易于维护.如果系统中有许多任务实现(即超过三个),您可以自动执行以下操作:
var taskTypes = (
from type in typeof(ITask).Assemby.GetTypes()
where typeof(ITask).IsAssignableFrom(type)
where !type.IsAbstract && !type.IsGenericTypeDefinition
select type)
.ToList();
// Register all as task types singleton
taskTypes.ForEach(type => container.Register(type, type, Lifestyle.Singleton));
// registers a list of all those (singleton) tasks.
container.RegisterAll<ITask>(taskTypes);
Run Code Online (Sandbox Code Playgroud)
或者,使用Simple Injector 2.3及更高版本,您可以将Registration实例直接传递给RegisterAll方法:
var taskTypes =
from type in typeof(ITask).Assemby.GetTypes()
where typeof(ITask).IsAssignableFrom(type)
where !type.IsAbstract && !type.IsGenericTypeDefinition
select type;
// registers a list of all those (singleton) tasks.
container.RegisterAll(typeof(ITask),
from type in taskTypes
select Lifestyle.Singleton.CreateRegistration(type, type, container));
Run Code Online (Sandbox Code Playgroud)
但是,这确实假设所有这些任务实现都有一个公共构造函数,并且所有构造函数参数都是可解析的(没有配置值,如int和string).如果不是这种情况,可以通过多种方式更改框架的默认行为,但如果您想了解有关此问题的任何内容,最好将该讨论移至新的SO问题.
再说一遍,如果我对你感到烦恼,我很抱歉,但我宁愿惹恼一些开发人员而不是错过帮助其他人的机会:-)