该文章着眼于Microsoft.Extensions.DependencyInjection
中提供的默认/内置ASP.NET Core依赖注入容器的GetService<T>()
和GetRequiredService<T>()
方法。
GetService()
返回为null
,而GetRequiredService()
会抛出一个异常,如果服务不存在。如果你使用的是第三方容器,请尽可能使用GetRequiredService()
,因为如果发生异常,第三方容器也许会提供诊断信息,以便确定未如预期般注册服务的原因。
1. 容器核心:IServiceProvider接口
IServiceProvider
接口是ASP.NET Core依赖注入抽象的核心。该接口实际上是System
命名空间中基类库的一部分。它很简单:
public interface IServiceProvider
{
object GetService(Type serviceType);
}
将所有类注册到DI容器(使用IServiceCollection
)后,DI容器需要作的就是允许使用GetService()
获取对象的实例。
当然,通常你根本就不应该在代码中直接使用IServiceProvider
。相反,应该使用标准的构造函数注入,并让框架去关心使用IServiceProvider
。
这是直接使用``是服务定位器模式的一个示例:http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/,因为它隐藏了类的依赖项。
尽管如此,有时候别无选择,比如,如果视图将服务注入属性中,或者在配置DI容器时使用转发类型,则需要直接使用IServiceProvider
。
2. 比较GetService()和GetRequiredService()
现在大家都不用.NET 1.0一样,如果要从IServiceProvider
获取服务,则可能要用泛型的GetService<T>()
扩展方法,而不是GetService(Type)
接口方法。类似的还有GetRequiredService<T>()
扩展方法,现在的问题是,它俩到底有啥区别?
首先,看看GetService()
方法的描述:
GetService()
返回类型为serviceType
,如果没有类型为serviceType
的服务对象,则返回null
。
再看看GetRequiredService()
的文档:
GetRequiredService()
返回类型为serviceType
的服务对象,如果没有,则抛出一个InvalidOperationException
异常。
因此,当请求的serviceType
实例可用时,这两种方法的行为相同,如果serviceType
尚未注册,它们的行为则不同:
GetService
- 返回null
GetRequiredService
- 抛异常
现在来看看代码,Microsoft.Extensions.DependencyInjection.Abstractions
库中的类ServiceProviderServiceExtensions
实现了GetService<T>()
和GetRequiredService<T>()
的泛型版本。在GitHub上查看代码
# 泛型
public static class ServiceProviderServiceExtensions
{
public static T GetService<T>(this IServiceProvider provider)
{
return (T)provider.GetService(typeof(T));
}
public static T GetRequiredService<T>(this IServiceProvider provider)
{
return (T)provider.GetRequiredService(typeof(T));
}
}
# 非泛型
public static class ServiceProviderServiceExtensions
{
public static object GetRequiredService(this IServiceProvider provider, Type serviceType)
{
var requiredServiceSupportingProvider = provider as ISupportRequiredService;
if (requiredServiceSupportingProvider != null)
{
return requiredServiceSupportingProvider.GetRequiredService(serviceType);
}
var service = provider.GetService(serviceType);
if (service == null)
{
throw new InvalidOperationException(Resources.FormatNoServiceRegistered(serviceType));
}
return service;
}
}
该方法第一步是检查IServiceProvider
是否也实现了ISupportRequiredService
接口。该接口提供了基础的非泛型版``的实现,因此,如果服务提供者实现了该接口,则直接调用GetRequiredService()
。
ASP.NET Core内置的DI容器没有实现
ISupportRequiredService
,只有第三方容器实现了GetRequiredService()
。
3. 该用哪个方法呢?
在你自己的代码中使用IServiceProvider
,通常是使用服务定位器反模式的标志,因此应该避免使用它。但是,由于设计限制(比如,不能在属性中使用DI)或作为DI容器配置本身的一部分而有必要的情况下,应该使用哪个呢?
基于GitHub上最初要求添加GetRequiredService()
的问题,笔者认为几乎所有情况下应该使用:
Use
GetRequiredService()
这有很多优点:
- 减少重复逻辑,如果服务不可用,则使用
GetRequiredService()
会立即引发异常。如果改用GetService()
,则需要检查调用代码中是否为null
,这个空检查代码会在各处重复调用。 - 快速失败,如果在使用
GetService()
忘记做空检查了,那么一段时间后可能会遇到NullReferenceException
。找出空引用的异常往往比显示地抛InvalidOperationException
异常花更多的时间。 - 允许对第三方DI容器进行高级诊断,StructureMap和其他第三方容器的最大好处之一就是它们能提供有关为何找不到服务的详细的异常信息。如果使用的是
GetRequiredService()
,则第三方容器本身会生成异常,并且可以提供额外的特定容器的信息,仅仅使用GetService()
返回null
,则不会给出更多消息了,这也是引入``的主要原因。
我看过一些反对使用``的观点,但我认为这些观点都不值得审查:
- “我没有用第三方容器”。如果使用的是内置容器(未实现
ISupportRequiredService
),那么使用GetRequiredService()
不会从任何额外的诊断中得到好处。不过,我认为前两个优点依然让GetRequiredService()
值得使用。加上如果以后使用第三方容器,那么你已经在进行最佳实践了。 - “我有可选服务,有时仅在DI容器中注册。” 这可能是使用GetService()的唯一理由。如果您的代码仅在注册给定服务时才应运行,则可能需要使用
GetService()
。但是,我也看到过,如果GetService()
返回null
,则使用候选服务。我认为,这很少是应用程序代码的良好模式。后备编排应该是在DI容器配置中发生的事情,而不是在使用服务的地方。
4. 总结
GetService()
是IServiceProvider
(ASP.NET Core DI抽象中的中心接口)上的唯一方法。第三方容器还可以实现可选接口ISupportRequiredService
,该接口提供GetRequiredService()
方法。当请求的serviceType
可用时,这些方法的行为相同。如果该服务不可用(即未注册),则GetService()
返回null
,而GetRequiredService()
则抛出InvalidOperationException
。
与GetService()
相比,GetRequiredService()
的主要好处是,当请求的服务不可用时,它允许第三方容器提供额外的诊断信息。因此,在使用第三方容器时,最好始终使用GetRequiredService()
。就个人而言,即使仅使用内置的DI容器,我也会在任何地方使用它。
英译中水平太次,勉强看吧 -^-
原文链接:https://andrewlock.net/the-difference-between-getservice-and-getrquiredservice-in-asp-net-core/