Alva

Alva

programmer
github
telegram
email

dotnet 中的依賴注入-05.註冊方法

本文示例代码,均采用 .NET 6,具体的代码可以在这个仓库 Articles.DI 中获取。

透過前面的文章,了解到了服務的三種聲明週期。那麼如果我們需要註冊服務時,框架都提供了哪些 API 呢?當我們要根據自身需求來聲明服務,聲明具體的實現時,又該如何編寫代碼呢?本文將探討 .NET 內置的 DI 框架提供的 API,以及其使用方法,具體作用。

.NET API 瀏覽器,提供了詳細的 API 文檔。這個鏈接就展示了註冊服務相關的 API。

註冊方法#

註冊服務的方法,肯定不止Add{LifeTime}<TService, TImplementation>()這一個方法,具體的方法類型,可以見點擊這個鏈接,查看文檔提供的表格,我在下面直接將其粘貼出來。

方法自動釋放多種實現傳遞參數
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
Add{LIFETIME}<{IMPLEMENTATION}>()
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})
AddSingleton(new {IMPLEMENTATION})

上面的表格,只列出了支持泛型的方法,還有形如Add{LIFETIME}(typeof({SERVICE}), typeof({IMPLEMENTATION}))的方法,其本質和泛型方法一樣,這裡就不列出來了。表格中右邊三列,分別描述了這些方法的局限範圍。

服務釋放#

容器負責服務的構建,但是如果一個服務到期,又是誰來負責服務的釋放?是服務的依賴者?還是容器?

前面表格中的自動釋放一列,如果為,則表明通過這類方法註冊的服務,由容器統一進行服務的釋放,如果這些服務中有實現IDisposable接口,那麼也由容器來自動調用Dispose方法,無需在代碼中顯式調用釋放方法。

查看表格,可以看到AddSingleton<{SERVICE}>(new {IMPLEMENTATION})AddSingleton(new {IMPLEMENTATION})兩類方法中,是不會自動釋放的,容器框架不會去自行釋放,那麼就需要開發人員自行負責服務的釋放。

多種實現#

前面的例子中,我們註冊時都是一個接口,一個實現類,在依賴函數注入時,注入的都是單個服務。如果我們想要將多個實現,都註冊為同一種接口,又該使用哪些方法呢?

前面表格中可以看到在多種實現這一列,有兩類方法不支持多種實現,Add{LIFETIME}<{IMPLEMENTATION}>()AddSingleton(new {IMPLEMENTATION})。其實查看方法描述就可以了解到,前者註冊時,直接將IMPLEMENTATION類的實現,註冊為IMPLEMENTATION類型的服務,沒法做到多種實現。而後者也是類似,直接將一個註冊實例,自然也沒法做到多種實現

這裡的多種實現,指的是實現了同一種接口的多個實現類,在註冊服務的時候,同時註冊到這個接口上。在上一篇文章中,我們是用一個實現類實現了多種接口,在註冊服務的時候,這個類同時註冊了多個接口,這兩種情況是不一樣的,後者不算多種實現

接下來我們直接使用代碼,來展示如何做到多種實現

// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService4
// 1. 設定一個接口 IMyDependency,以及實現這個接口的多個實現類
public interface IMyDependency
{
    string Id { get; }

    bool Flag { get; }
}

public class MyDependency0 : IMyDependency
{
    public string Id { get; } = Guid.NewGuid().ToString()[^4..];

    public bool Flag { get; init; } = false;
}

public class MyDependency1 : MyDependency0 {}
public class MyDependency2 : MyDependency0 {}
public class MyDependency3 : MyDependency0 {}
public class MyDependency4 : MyDependency0 {}
// 2. 註冊服務
using Microsoft.Extensions.DependencyInjection.Extensions;

IHost host = Host.CreateDefaultBuilder(args)
   .ConfigureServices(services =>
    {
        // 同時註冊一個接口的多種實現
        services.AddTransient<IMyDependency, MyDependency1>();
        services.AddTransient<IMyDependency, MyDependency2>();

        // TryAdd 如果接口已被註冊過,不會再註冊
        // IMyDependency 已經被註冊過,所以並不會將 MyDependency3 註冊為它的實現
        services.TryAddTransient<IMyDependency, MyDependency3>();

        // TryAddEnumerable 如果接口的同一種實現被註冊過,則不會重複註冊
        // IMyDependency -> MyDependency4,這個關係沒有被註冊過,所以可以成功註冊
        var descriptor = new ServiceDescriptor(
            typeof(IMyDependency),
            typeof(MyDependency4),
            ServiceLifetime.Transient);
        services.TryAddEnumerable(descriptor);

        // 不會檢查是否註冊過這個接口,也不會檢查是否註冊過這個關係,會重複註冊
        // MyDependency2 被註冊過兩次
        // 那麼在獲取 IEnumerable<IMyDependency> 時
        // MyDependency2 類型的實例會有兩個
        services.AddTransient<IMyDependency, MyDependency2>(_ => new MyDependency2
        {
            Flag = true // 單獨使用工廠方法構造這個服務,用於區分最後一次註冊的服務
        });
    })
   .Build();

Fun(host.Services);

static void Fun(IServiceProvider serviceProvider)
{
    using var scopeServices = serviceProvider.CreateScope();
    var       services      = scopeServices.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    GetData(myDependency);
    Console.WriteLine();

    var list = services.GetRequiredService<IEnumerable<IMyDependency>>();
    foreach (var dependency in list)
    {
        GetData(dependency);
    }
    Console.WriteLine();
}

static void GetData<T>(T item) where T : IMyDependency
{
    Console.WriteLine($"{item.GetType().Name} {item.Id}, {item.Flag}");
}

程序的運行結果如下:

MyDependency2 c432, True

MyDependency1 ea48, False
MyDependency2 9b9a, False
MyDependency4 c4ce, False
MyDependency2 77e9, True

查看整個程序的輸出結果,因為將所有服務註冊為Transient類型的聲明週期,所以每個實例的 Id 都不一樣。

查看第一行輸出中,item.Flag 為True,也就是說在解析IMyDependency類型時,最後一次成功註冊的實現類將會被構造並注入(即本例中的myDependency變量)。

對比下面四行輸出,其順序正好就是服務註冊時的順序,即獲取IEnumerable<IMyDependency>類型的變量時,會按照註冊順序,將所有成功註冊的類型,統統注入進去。而這裡面的第二行和第四行,都是MyDependency2,說明註冊服務,是可以重複註冊同一實現的。

傳遞參數#

方法自動釋放多種實現傳遞參數
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
Add{LIFETIME}<{IMPLEMENTATION}>()
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})
AddSingleton(new {IMPLEMENTATION})

傳遞參數其實就是容器在構造服務實例時,是否可以使用工廠方法,使用多樣化的方式構造實例,而非單純調用構造函數。查看這個表格,若是傳遞了工廠方法的,其傳遞參數列都為,反之則為。實際上,即使不傳遞工廠方法,也可以用其他途徑,做到傳遞參數的目的。

比如在構造函數中使用IOperation<>類型。將需要的配置數據,註冊到IOperation<MyOperation>中,就可以在構造函數中,拿到需要的實時數據,從而達到傳遞參數的目的。而具體配置數據的獲取,則可以放在IOperation<MyOperation>的註冊方法中。

參考鏈接#

.NET 中的依賴關係注入

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。