正如您已经注意到的,TryAddSingleton 和 AddSingleton 之间的区别在于 AddSingleton 始终将注册附加到集合中,而 TryAddSingleton 仅在给定服务类型不存在注册时才这样做。
当同一服务类型存在多个注册,但请求一个实例时,.NET Core 将始终返回最后一个注册。这意味着AddSingleton的行为是有效地替换非集合解析的实例,例如:
services.AddSingleton<IX, A>();
services.AddSingleton<IX, B>(); // ‘replaces’ A
IX x = container.GetService<IX>(); // resolves B
然而,对于集合解析,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 only
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 的行为可能非常棘手,因为它隐含地覆盖了现有注册,而没有任何警告。我的经验是,这种行为可能会导致难以发现配置错误。更安全的设计应该是使用AddSingleton、AppendSingleton 和ReplaceSingleton 方法,其中AddSingleton 会在注册存在的情况下抛出异常,而ReplaceSingleton 实际上会丢弃现有的注册。