何时使用TryAddSingleton或AddSingleton?

时间:2018-01-10 10:58:48

标签: c# dependency-injection asp.net-core startup

我在一些.net核心示例中注意到,在注册服务时会调用TryAddSingleton,有些AddSingleton会调用。

Decompiler显示TryAdd(由TryAddSingleton调用) 添加指定的参数"描述符"收集"收集"如果服务类型尚未注册。

这是否意味着使用TryAddSingleton总是更安全,以防某些其他方法/库已经注册了同一个类?

1 个答案:

答案 0 :(得分:23)

正如您已经注意到的,TryAddSingletonAddSingleton之间的区别在于AddSingleton始终将注册附加到集合中,而TryAddSingleton仅在没有注册时执行此操作注册给定的服务类型。

如果同一服务类型存在多个注册,但请求了一个实例,则.NET Core将始终返回最后一个。这意味着AddSingleton的行为是替换非集合解析的实例。例如:

services.AddSingleton<IX, A>();
services.AddSingleton<IX, B>(); // ‘replaces’ A
IX x = container.GetService<IX>(); // resolves B

然而,对于集合结果,AddSingleton表现完全不同,因为在这种情况下AddSingleton表现为该服务类型的现有注册的集合“附加”。例如:

services.AddSingleton<IX, A>();
services.AddSingleton<IX, B>();
IEnumerable<IX> xs = container.GetServices<IX>(); // resolves A and B

但是,对于TryAddSingleton,当已存在给定服务类型的注册时,将不会添加注册。这意味着,无论何时将服务类型解析为一个实例或实例集合,在至少有一个注册时都不会添加注册。例如:

services.TryAddSingleton<IX, A>(); // adds A
services.TryAddSingleton<IX, B>(); // does not add B, because of A
IX x = container.GetService<IX>(); // resolves A

services.TryAddSingleton <IX, A>(); // adds A
services.TryAddSingleton <IX, B>(); // does not add B, because of A
IEnumerable<IX> xs = container.GetServices<IX>(); // resolves A

TryAddSingleton对于希望将自己的组件注册到容器的框架和第三方库代码特别有用。它允许应用程序开发人员覆盖框架或库的默认注册,即使应用程序开发人员在调用框架或第三方AddXXX扩展方法之前注册了该组件。例如:

services.TryAddSingleton<IX, A>(); // adds A
services.AddThirdPartyLibrary (); // calls services.TryAddSingleton <IX, B>();
IX x = container.GetService<IX>(); // resolves A

第三方库是否已调用AddSingleton而不是TryAddSingleton,应用程序开发人员的A将始终被覆盖,这可能会让开发人员感到困惑。 作为应用程序开发人员,您通常知道自己注册的内容,这使得使用TryAddSingleton对应用程序开发人员无用。

我甚至认为,从应用程序开发人员的角度来看,AddSingleton的行为可能非常棘手,因为它隐含地覆盖了现有的注册,没有任何警告。我的经验是,这种行为可能导致很难发现配置错误。更安全的设计应该是AddSingletonAppendSingletonReplaceSingleton方法,其中AddSingleton会在注册存在的情况下抛出异常,ReplaceSingleton会实际上扔掉了现有的注册。