ASP.NET Core中GetService()和GetRequiredService()之間的區別

依樂祝發表於2019-07-01

上篇文章《在.NET Core 3.0中的WPF中使用IOC圖文教程》中,我們嘗試在WPF中應用.NET Core內建的IOC進行程式設計,在解析MainWindow的時候我用了GetRequiredService<T>()方法,當時就在想這個GetRequiredService<T>()方法跟GetService<T>()到底有什麼區別呢,於是乎,谷歌了一把,就發現了一篇文章來介紹他們區別的,於是乎嘗試翻譯一把,希望對大家有所幫助。文章最後會給出原文連結,以下就是翻譯內容:

作者:依樂祝
原文地址:https://www.cnblogs.com/yilezhu/p/11107648.html


本文將介紹Microsoft.Extensions.DependencyInjection中提供的預設/內建ASP.NET Core DI容器的方法GetService<T>()GetRequiredService<T>()方法。我將描述它們之間的差異以及您應該使用哪種方法。

如果服務不存在則GetService()返回nullGetRequiredService()而是丟擲異常。如果您正在使用第三方容器,請儘可能使用GetRequiredService- 如果發生異常,第三方容器可能就會根據異常資訊提供相應的診斷資訊,以便您可以找出未註冊預期服務的原因

容器的核心 - IServiceProvider介面

ASP.NET Core依賴注入抽象的核心是IServiceProvider介面。該介面實際上是System名稱空間中基類庫的一部分。介面本身很簡單:

public interface IServiceProvider
{
    object GetService(Type serviceType);
}

一旦您使用DI容器(使用IServiceCollection)註冊了所有類,幾乎所有DI容器需要做的就是允許您使用GetService()查詢物件的例項。

當然,您通常根本不應該直接在程式碼中使用IServiceProvider。相反,您應該使用標準的建構函式注入,並讓框架來承載並在幕後使用IServiceProvider

直接使用IServiceProvider是服務定位器模式的一個示例。這通常被認為是反模式,因為它隱藏了類的依賴關係。

然而,有些時候你沒有選擇的餘地。例如,如果您試圖將服務注入到屬性,或者在配置DI容器時使用“轉發”型別,則需要直接使用IServiceProvider

比較GetService ()和GetRequiredService ()

鑑於我們不再使用.NET 1.0,如果你想從IServiceProvider中檢索服務,你可能使用了通用的泛型GetService<T>()擴充套件方法,而不是GetService(Type)介面方法。但是你可能也注意到了類似的GetRequiredService<T>()擴充套件方法 - 問題是,它們之間有什麼區別呢,您應該使用哪種方法?

在我們研究任何程式碼之前,讓我們先討論一下這些方法的預期行為。首先,從GetService()方法的文件開始:

GetService()返回一個serviceType型別的服務物件。如果返回的是一個沒有型別的服務物件serviceType則返回null

GetRequiredService()的文件內容進行對比:

GetRequiredService()返回一個serviceType型別的服務物件。如果沒有serviceType型別的服務,則丟擲一個InvalidOperationException異常。

因此,當請求的例項serviceType可用時,兩種方法的行為都相同。不同之處在於serviceType未註冊時的行為:

  • GetService- 如果服務未註冊,則返回null
  • GetRequiredService- 如果服務未註冊,則丟擲一個Exception異常。

現在我們已經清楚了,讓我們看看一些程式碼。

ServiceProviderServiceExtensions班上Microsoft.Extensions.DependencyInjection.Abstractions庫中同時實現了通用版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));
    }
}

這兩種方法實際上都是相同的 - 通用擴充套件方法委託給非泛型版本的GetService()GetRequiredService()。它們只是一種便利,因此您在自己的程式碼中不需要使用更多的typeof()和型別轉換。

非泛型版本的GetService()IServiceProvider介面的一部分,但非泛型GetRequiredService()實現是同一類中的擴充套件方法:

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實現,因此如果服務提供者實現它,GetRequiredService()則可以直接呼叫。

ASP.NET Core內建的DI容器並沒有實現ISupportRequiredService- 只有第三方容器實現了GetRequiredService()

如果IServiceProvider沒有實現ISupportRequiredService,則執行所需的異常丟擲行為,如您所料:GetService()呼叫,如果返回null則丟擲異常。

那你應該使用哪種方法?

正如我之前所說,理想情況下,兩者都可以!

在您自己的程式碼使用ISeviceProvider通常是你正在使用服務定位器反模式的一個標誌,所以一般應避免使用ISeviceProvider。但是,如果由於設計限制而需要(例如,您不能在屬性中使用DI),或者作為DI容器配置本身的一部分的情況下,您應該使用哪一種呢?

基於GitHub中要求新增GetRequiredService()的原始問題,以及Jeremy D. Miller先前提出的問題 ,我認為幾乎所有情況下的規則是:

使用 GetRequiredService()

  • 減少重複。如果服務不可用,則使用GetRequiredService()會立即丟擲異常。如果您使用GetService(),那麼您需要在呼叫程式碼中檢查是否為null,並且通常需要丟擲異常。那個空檢查程式碼需要在任何地方重複。
  • 失敗很快。如果您在使用GetService()時忘記檢查是否為null,那麼稍後您的程式可能會以NullReferenceException結束。找出導致異常的原因總是比顯式的告訴你的InvalidOperationException更困難,需要做更多的工作。
  • 允許對第三方容器進行高階診斷。StructureMap和其他一些第三方容器的一大好處是,它們能夠提供詳細的異常訊息,說明為什麼找不到服務。如果您正在使用GetRequiredService(),則第三方容器本身會生成異常,因此可以提供其他特定於容器的資訊。只返回null(帶GetService())不會給你進一步的詳細的資訊。這是引入GetRequiredService()的主要原因

當然,我已經看到了一些反對GetRequiredService()`的觀點,但我認為其中任何一個都不會受到審查:

  • “我沒有使用第三方容器”。如果您正在使用內建容器(未實現ISupportRequiredService),那麼您將無法通過使用任何其他診斷獲益GetRequiredService()。但是,我認為前兩個優勢仍然存在,並使GetRequiredService值得使用。此外,如果您以後新增第三方容器,您已經在使用最佳實踐了。
  • “我有可選服務,有時只在DI容器中註冊。” 。這可能是使用GetService()唯一有效的理由。如果您的程式碼只有在註冊了給定服務時才能執行,那麼您可能需要使用GetService()。但是,如果GetService()返回NULL,我也看到它在使用回退服務時使用。在我看來,這很少是應用程式程式碼的好模式。回退的編排應該是DI容器配置的一部分,而不是使用服務的位置。

所以,現在你有了 - GetService()GetRequiredService()之間的對比了。在我進一步挖掘它之前,當我選擇一個而不是另一個時,我有點武斷,但現在我會確保我總是理所當然的使用GetRequiredService()

摘要

GetService()IServiceProvider上的唯一方法,ISeviceProvider是ASP.NET核心DI抽象中的中央介面。第三方容器還可以實現可選介面ISupportRequiredService,該介面提供GetRequiredService()方法。當請求的型別serviceType可用時,這些方法的行為相同。如果服務不可用(即它沒有註冊),則GetService()返回null,而GetRequiredService()丟擲一個InvalidOperationException

GetRequiredService()相對於GetService()的主要好處是當服務不可用時,它允許第三方容器提供額外的診斷資訊。因此,在使用第三方容器時最好使用GetRequiredService()。就個人而言,我會在任何地方使用它,即使我只使用內建的DI容器。

原英文連結:https://andrewlock.net/the-difference-between-getservice-and-getrquiredservice-in-asp-net-core/

相關文章