2025年1月14日 星期二

Azure OpenAI AOAI 2.0 : 12 使用 Phi-3.5-vision 與 Meta-Llama-3-1-70B 進行聊天設計教學

Azure OpenAI AOAI 2.0 : 12 使用 Phi-3.5-vision 與 Meta-Llama-3-1-70B 進行聊天設計教學

建立測試專案

請依照底下的操作,建立起這篇文章需要用到的練習專案

  • 打開 Visual Studio 2022 IDE 應用程式
  • 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗右半部
    • 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
    • 切換 [所有專案類型 (T)] 下拉選單控制項為 [主控台]
  • 在中間的專案範本清單中,找到並且點選 [主控台應用程式] 專案範本選項

    專案,用於建立可在 Windows、Linux 及 macOS 於 .NET 執行的命令列應用程式

  • 點選右下角的 [下一步] 按鈕
  • 在 [設定新的專案] 對話窗
  • 找到 [專案名稱] 欄位,輸入 csInference 作為專案名稱
  • 在剛剛輸入的 [專案名稱] 欄位下方,確認沒有勾選 [將解決方案與專案至於相同目錄中] 這個檢查盒控制項
  • 點選右下角的 [下一步] 按鈕
  • 現在將會看到 [其他資訊] 對話窗
  • 在 [架構] 欄位中,請選擇最新的開發框架,這裡選擇的 [架構] 是 : .NET 8.0 (長期支援)
  • 在這個練習中,需要去勾選 [不要使用最上層陳述式(T)] 這個檢查盒控制項

    這裡的這個操作,可以由讀者自行決定是否要勾選這個檢查盒控制項

  • 請點選右下角的 [建立] 按鈕

稍微等候一下,這個 背景工作服務 專案將會建立完成

安裝要用到的 NuGet 開發套件

因為開發此專案時會用到這些 NuGet 套件,請依照底下說明,將需要用到的 NuGet 套件安裝起來。

安裝 Azure.AI.Inference 套件

請依照底下說明操作步驟,將這個套件安裝到專案內

  • 滑鼠右擊 [方案總管] 視窗內的 [專案節點] 下方的 [相依性] 節點
  • 從彈出功能表清單中,點選 [管理 NuGet 套件] 這個功能選項清單
  • 此時,將會看到 [NuGet: csInference] 視窗
  • 切換此視窗的標籤頁次到名稱為 [瀏覽] 這個標籤頁次
  • 在左上方找到一個搜尋文字輸入盒,在此輸入 Azure.AI.Inference

    與使用 Azure OpenAI 用安裝 Azure.AI.OpenAI 套件不同,這裡需要安裝 Azure.AI.Inference 這個套件

  • 在視窗右方,將會看到該套件詳細說明的內容,其中,右上方有的 [安裝] 按鈕

    請確認有取消 Pre-release 這個選項,與選擇 2.0 正式版

  • 點選這個 [安裝] 按鈕,將這個套件安裝到專案內

修改 Program.cs 類別內容

在這篇文章中,將會把會用到的新類別與程式碼,都寫入到 [Program.cs] 這個檔案中,請依照底下的操作,修改 [Program.cs] 這個檔案的內容

  • 在專案中找到並且打開 [Program.cs] 檔案
  • 將底下的程式碼取代掉 Program.cs 檔案中內容
using Azure.AI.Inference;
using Azure;
using System.ClientModel.Primitives;

namespace csInference
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string apiKeyAzureLlama3170B = System.Environment.GetEnvironmentVariable("AzureLlama3170B");
            string apiKeyAzurePhi35Vision = System.Environment.GetEnvironmentVariable("AzurePhi35Vision");
            Console.WriteLine($"與 Meta 的 Llama LLM 聊天");
            Chart("https://Meta-Llama-3-1-70B-Instruct-wcwg.eastus2.models.ai.azure.com",
                 apiKeyAzureLlama3170B);
            Console.WriteLine($"與 Microsoft 的 Phi LLM 聊天");
            Chart("https://Phi-3-5-vision-instruct-havbt.eastus2.models.ai.azure.com",
                apiKeyAzurePhi35Vision);
        }

        private static void Chart(string endpoint, string key)
        {
            ChatCompletionsClient client = new ChatCompletionsClient(
                new Uri(endpoint),
                new AzureKeyCredential(key)
            );

            var requestOptions = new ChatCompletionsOptions()
            {
                Messages = {
        new ChatRequestUserMessage("'''就醫時間為 2020-2022 年之間,具有糖尿病診斷且為門診就醫的搜尋條件'''") ,
        new ChatRequestUserMessage("請將上述需求,使用底下 JSON 格式來表達,只需要生成 JSON,不需要其他說明") ,
        new ChatRequestUserMessage("{\r\n  // 醫療紀錄搜尋條件\r\n  \"BaseCondition\": {\r\n    \"BeginDate\": \"\", // 就醫紀錄搜尋開始時間\r\n    \"EndDate\": \"\",  // 就醫紀錄搜尋結束時間\r\n    \"Department\": \"\",      // 就醫紀錄搜尋科別 內科,心臟科\r\n    \"RecordType\": \"\"       // 就醫紀錄搜尋類型 門診,急診,住院\r\n  }\r\n  \"ICD10\": [] // ICD10疾病碼 搜尋條件\r\n  \"Medicine\": [] // 藥品名稱 搜尋條件\r\n}") },
                AdditionalProperties = { { "logprobs", BinaryData.FromString("false") } },
            };

            var response = client.Complete(requestOptions);
            Console.WriteLine($"Response:\n {response.Value.Content}");
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine(new string('-',40));
            Console.WriteLine();
            Console.WriteLine();
        }
    }
}

在程式一開始啟動時,會使用 [System.Environment.GetEnvironmentVariable] 方法來讀取環境變數 AzureLlama3170B 與 AzurePhi35Vision 的值,這兩個值是 Azure Llama 3170B 與 Azure Phi 35 Vision 的 API Key

接著將會分別把這兩個 Key 傳入到 [Chart] 這個方法內,其中第一個傳入的引數將會為要使用推論用的 LLM 服務端點,第二個則是 API Key

在 [Chart] 方法內,會建立一個 [ChatCompletionsClient] 的物件,並且傳入服務端點與 API Key

接著建立一個 [ChatCompletionsOptions] 的物件,並且設定 [Messages] 屬性,這裡設定了三個 [ChatRequestUserMessage] 的物件,分別是要求生成 JSON 的需求,底下將會是原始的 Prompt 文字

'''就醫時間為 2020-2022 年之間,具有糖尿病診斷且為門診就醫的搜尋條件'''
請將上述需求,使用底下 JSON 格式來表達,只需要生成 JSON,不需要其他說明
{
  // 醫療紀錄搜尋條件
  "BaseCondition": {
    "BeginDate": "", // 就醫紀錄搜尋開始時間
    "EndDate": "",  // 就醫紀錄搜尋結束時間
    "Department": "",      // 就醫紀錄搜尋科別 內科,心臟科
    "RecordType": ""       // 就醫紀錄搜尋類型 門診,急診,住院
  }
  "ICD10": [] // ICD10疾病碼 搜尋條件
  "Medicine": [] // 藥品名稱 搜尋條件
}

最後呼叫 [client.Complete] 方法,並且傳入這個 [ChatCompletionsOptions] 的物件,這個方法將會回傳一個 [ChatCompletion] 的物件,這個物件將會包含了生成的 JSON 內容

執行測試專案

  • 按下 F5 開始執行專案
  • 將會看到輸出結果
與 Meta 的 Llama LLM 聊天
Response:

{ "BaseCondition": { "BeginDate": "2020-01-01", "EndDate": "2022-12-31", "Department": "", "RecordType": "門診" }, "ICD10": [ { "Code": "E11", "Desc": "2型糖尿病" } ], "Medicine": [] }



----------------------------------------


與 Microsoft 的 Phi LLM 聊天
Response:

{
  "BaseCondition": {
    "BeginDate": "2020-2022", // 搜尋條件開始的時間
    "EndDate": "", // 搜尋條件結束的時間
    "Department": "門診", // 搜尋條件的科別
    "RecordType": "門診" // 搜尋條件的類型
  },
  "ICD10": [], // 搜尋條件的糖尿病碼
  "Medicine": [] // 搜尋條件的藥品名稱
}


任何其他需求,?告訴我,我會盡力幫助您。


---------------------------------------- 

這樣就完成了這個教學,這個教學將會展示如何使用 Azure.AI.Inference 來進行聊天設計,這個教學將會展示如何使用 Phi-3.5-vision 與 Meta-Llama-3-1-70B 進行聊天設計 





2025年1月13日 星期一

在 .NET9 的相依性注入三種方式的使用上差異

在 .NET9 的相依性注入三種方式的使用上差異

在這篇文章中,將會透過一個 ASP.NET Core 專案,建立一個 控制器 Controller,分別來進行不同相依性注入生命週期的表現,透過這篇文章,來瞭解透過不同方式來注入(建構式注入與服務定位器)或者取得不同生命週期(Transient、Scoped 和 Singleton)的物件,在實際使用上,會有甚麼差異。底下將會是預計要做的評估做法。

  • 在 控制器內,使用建構式注入了 Transient、Scoped 和 Singleton 三個服務
  • 在 控制器內,使用建構式注入了 TogetherMyService 這個服務
  • 在 [TogetherMyService] 類別內,使用建構式注入了 Transient、Scoped 和 Singleton 三個服務
  • 在 [TogetherMyService] 類別內,使用建構式注入了 IServiceProvider 服務定位器
  • 在 [TogetherMyService] 類別內,使用服務定位器取得了 Transient、Scoped 和 Singleton 三個服務

建立測試專案

請依照底下的操作,建立起這篇文章需要用到的練習專案

  • 打開 Visual Studio 2022 IDE 應用程式
  • 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗右半部
    • 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
    • 切換 [所有專案類型 (T)] 下拉選單控制項為 [Web API]
  • 在中間的專案範本清單中,找到並且點選 [ASP.NET Core Web API] 專案範本選項

    此專案範本可用於 ASP.NET Core 控制器或最小 API 建立 RESTful Web API,並可選擇性支援 OpenAPI 和驗證

  • 點選右下角的 [下一步] 按鈕
  • 在 [設定新的專案] 對話窗
  • 找到 [專案名稱] 欄位,輸入 csDIRegistrationDifferent 作為專案名稱
  • 在剛剛輸入的 [專案名稱] 欄位下方,確認沒有勾選 [將解決方案與專案至於相同目錄中] 這個檢查盒控制項
  • 點選右下角的 [下一步] 按鈕
  • 現在將會看到 [其他資訊] 對話窗
  • 在 [架構] 欄位中,請選擇最新的開發框架,這裡選擇的 [架構] 是 : .NET 9.0 (標準字詞支援)
  • 在 [驗證類型] 選擇無
  • 在這個練習中,需要去勾選 [不要使用最上層陳述式(T)] 這個檢查盒控制項

    這裡的這個操作,可以由讀者自行決定是否要勾選這個檢查盒控制項

  • 請點選右下角的 [建立] 按鈕

稍微等候一下,這個專案將會建立完成

建立一個要在控制器內注入的服務 TogetherMyService 類別

  • 滑鼠右擊專案節點
  • 選擇 [加入] > [類別]
  • 在對話窗下方的 [名稱] 欄位內,輸入 [TogetherMyService] 作為類別名稱
  • 點選 [新增] 按鈕
  • 在 [TogetherMyService] 類別內,輸入底下的程式碼
namespace csDIRegistrationDifferent
{
    public class MyService : ITransientMyService, ISingletonMyService, IScopedMyService
    {
        public int Value { get; set; }
        public DateTime CreateAt { get; set; }
        public MyService()
        {
            Value = new Random().Next(1000, 9999);
            CreateAt = DateTime.Now;
        }
    }


    public interface IBaseMyService
    {
        public int Value { get; set; }
        public DateTime CreateAt { get; set; }
    }

    public interface ISingletonMyService : IBaseMyService
    {
    }
    public interface IScopedMyService : ISingletonMyService
    {
    }

    public interface ITransientMyService : ISingletonMyService
    {
    }

    public class TogetherMyService
    {
        private readonly ITransientMyService transient;
        private readonly IScopedMyService scoped;
        private readonly ISingletonMyService singleton;
        private readonly IServiceProvider serviceProvider;
        private readonly ILogger<TogetherMyService> logger;

        public TogetherMyService(ITransientMyService transient,
            IScopedMyService scoped, ISingletonMyService singleton,
            IServiceProvider serviceProvider,
            ILogger<TogetherMyService> logger)
        {
            this.transient = transient;
            this.scoped = scoped;
            this.singleton = singleton;
            this.serviceProvider = serviceProvider;
            this.logger = logger;
        }

        public void Show()
        {
            var transientBy服務定位器 = serviceProvider.GetService<ITransientMyService>();
            var scopedBy服務定位器 = serviceProvider.GetService<IScopedMyService>();
            var singletonBy服務定位器 = serviceProvider.GetService<ISingletonMyService>();

            Console.WriteLine($"===  [From TogetherMyService ] ======================");
            Console.WriteLine($"TogetherMyService GetHashCode: {GetHashCode()}");
            Console.WriteLine($"=====================================================");
            Console.WriteLine($"Transient GetHashCode: {transient.GetHashCode()}");
            Console.WriteLine($"Transient Value: {transient.Value}");
            Console.WriteLine($"Transient服務定位器 Value: {transientBy服務定位器!.Value}");
            Console.WriteLine($"Transient Create Time: {transient.CreateAt}");
            Console.WriteLine($"---------------------------------");
            Console.WriteLine($"Scoped GetHashCode: {scoped.GetHashCode()}");
            Console.WriteLine($"Scoped Value: {scoped.Value}");
            Console.WriteLine($"Scoped服務定位器 Value: {scopedBy服務定位器!.Value}");
            Console.WriteLine($"Scoped Create Time: {scoped.CreateAt}");
            Console.WriteLine($"---------------------------------");
            Console.WriteLine($"Singleton GetHashCode: {singleton.GetHashCode()}");
            Console.WriteLine($"Singleton Value: {singleton.Value}");
            Console.WriteLine($"Singleto服務定位器n Value: {singletonBy服務定位器!.Value}");
            Console.WriteLine($"Singleton Create Time: {singleton.CreateAt}");
            Console.WriteLine($"");
            Console.WriteLine($"");
            Console.WriteLine($"");
        }
    }
}

在這個檔案中,將會建立一個 [IBaseMyService] 介面,這個介面有兩個屬性,分別是 [Value] 和 [CreateAt],這兩個屬性都是可以讀寫的屬性。

public interface IBaseMyService
{
    public int Value { get; set; }
    public DateTime CreateAt { get; set; }
}

接著,建立了三個介面 [ISingletonMyService]、[IScopedMyService] 和 [ITransientMyService],這三個介面都繼承自 [IBaseMyService] 介面。最後,建立了一個 [MyService] 類別,這個類別實作了 [ISingletonMyService]、[IScopedMyService] 和 [ITransientMyService] 介面,並且在建構式中,初始化了 [Value] 和 [CreateAt] 屬性的值。

因此,有了 Transient、Scoped 和 Singleton 三個介面,就可以分別實作出相對應的服務,接著進行不同的生命週期的服務表現差異比較,在這裡的設計做法,這三個服務都是使用 [MyService] 這個類別實作的,這裡使用的語法為 public class MyService : ITransientMyService, ISingletonMyService, IScopedMyService

除了會分別進行注入 Transient、Scoped 和 Singleton 三個服務外,注入一個 [TogetherMyService] 類別,並且在這個注入的物件內,再度同時注入 Transient、Scoped 和 Singleton 三個服務,並且在這個類別內,進行服務的取得,並且比較服務的取得方式,一個是透過建構式注入,另一個是透過服務定位器取得服務。

過服務定位器取得服務做法為,先透過建構式注入的方式,注入一個 [IServiceProvider] 服務定位器,接著透過這個服務定位器,取得 Transient、Scoped 和 Singleton 三個服務。其中在 .NET 框架內,[IServiceProvider] 這個介面是一個服務定位器,這個介面提供了一個方法 [GetService],這個方法可以透過服務的型別,取得服務的實例。因此,在因此,在[TogetherMyService] 類別內,將會有透過服務定位器取得了 TransientBy服務定位器、ScopedBy服務定位器 和 SingletonBy服務定位器 三個服務與透過建構式入的 Transient、Scoped 和 Singleton 三個服務。

在 [TogetherMyService] 類別內,還設計了一個 [Show] 方法,這個方法將會列印出 Transient、Scoped 和 Singleton 三個採用建構式注入的服務的值,以及服務的建立時間,並且也會列印出 TransientBy服務定位器、ScopedBy服務定位器 和 SingletonBy服務定位器 三個採用服務定位器取的服務的值,這樣可以更清楚的看到服務的生命週期。

總結不同注入測試方法

  • 控制器內,使用建構式注入了 Transient、Scoped 和 Singleton 三個服務
  • 控制器內,使用建構式注入了 TogetherMyService 這個服務
  • 在 [TogetherMyService] 類別內,使用建構式注入了 Transient、Scoped 和 Singleton 三個服務
  • 在 [TogetherMyService] 類別內,使用建構式注入了 IServiceProvider 服務定位器
  • 在 [TogetherMyService] 類別內,使用服務定位器取得了 Transient、Scoped 和 Singleton 三個服務

建立一個控制器

  • 滑鼠右擊 [專案] > [Controllers] 節點
  • 選擇 [加入] > [類別]
  • 在對話窗下方的 [名稱] 欄位內,輸入 [VerifyRegistrationDIController] 作為類別名稱
  • 點選 [新增] 按鈕
  • 在 [VerifyRegistrationDIController] 類別內,輸入底下的程式碼
using Microsoft.AspNetCore.Mvc;

namespace csDIRegistrationDifferent.Controllers;

[ApiController]
[Route("[controller]")]
public class VerifyRegistrationDIController : ControllerBase
{
    private readonly ILogger<VerifyRegistrationDIController> logger;
    private readonly TogetherMyService togetherMyService;
    private readonly ITransientMyService transient;
    private readonly IScopedMyService scoped;
    private readonly ISingletonMyService singleton;

    public VerifyRegistrationDIController(ILogger<VerifyRegistrationDIController> logger,
        TogetherMyService togetherMyService, ITransientMyService transient,
        IScopedMyService scoped, ISingletonMyService singleton)
    {
        this.logger = logger;
        this.togetherMyService = togetherMyService;
        this.transient = transient;
        this.scoped = scoped;
        this.singleton = singleton;
    }
    [HttpGet(Name = "GetVerifyRegistrationDI")]
    public void Get()
    {
        Console.WriteLine($"=== [From VerifyRegistrationDIController ] ======================");
        Console.WriteLine($"Transient Value: {transient.Value}");
        Console.WriteLine($"Scoped Value: {scoped.Value}");
        Console.WriteLine($"Singleton Value: {singleton.Value}");
        Console.WriteLine($"=================================================================");
        Console.WriteLine($"");
        Console.WriteLine($"");
        Console.WriteLine($"");
        togetherMyService.Show();
    }
}

在這個控制器 [VerifyRegistrationDIController] 內,使用建構式注入了 Transient、Scoped 和 Singleton 三個服務,並且也使用建構式注入了 TogetherMyService 這個服務,這樣就可以在這個控制器內,透過這些服務,來進行服務的操作。

對於這個 [Get()] 方法,這個方法將會列印出 Transient、Scoped 和 Singleton 三個服務的值,並且也會呼叫 [TogetherMyService] 類別內的 [Show()] 方法,這樣可以更清楚的看到服務的生命週期。

修改 Program.cs 類別內容

  • 找到並且打開 [Program.cs] 這個檔案
  • 使用底下程式碼替換掉這個檔案的內容
namespace csDIRegistrationDifferent;

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.

        builder.Services.AddControllers();
        // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
        builder.Services.AddOpenApi();

        builder.Services.AddTransient<TogetherMyService>();
        builder.Services.AddTransient<ITransientMyService, MyService>();
        builder.Services.AddScoped<IScopedMyService, MyService>();
        builder.Services.AddSingleton<ISingletonMyService, MyService>();

        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.MapOpenApi();
        }

        app.UseHttpsRedirection();

        app.UseAuthorization();


        app.MapControllers();

        app.Run();
    }
}

在這個 ASP.NET Core Web API 進入點內,將會底下程式碼,宣告不同類別的注入生命週期,以及使用不同介面(但是都會使用同一個具體實作類別) 宣告其要注入的生命週期。

builder.Services.AddTransient<TogetherMyService>();
builder.Services.AddTransient<ITransientMyService, MyService>();
builder.Services.AddScoped<IScopedMyService, MyService>();
builder.Services.AddSingleton<ISingletonMyService, MyService>();

使用 HTTP 檔案來協助做 API 測試

  • 在專案跟目錄下,找到並且打開 [csDIRegistrationDifferent.http] 這個檔案 

  • 此時在 VS2022 將會看到如下畫面

  • 在這個檔案內,將會有一些 HTTP 請求的範例,這些範例將會用來測試 API 的功能

  • 點選功能表 [檢視] > [其他視窗] > [端點總管]

  • 滑鼠右擊 [verifyregistrationdi] 節點,接著點選 [產生要求] 項目 

  • 此時將會看到 [csDIRegistrationDifferent.http] 的視窗,列出了這個 HTTP 請求的結果 

執行測試專案

  • 按下 F5 開始執行專案

  • 等候一下,當螢幕上開始顯示訊息時,表示專案已經開始執行

  • 點選功能表 [檢視] > [其他視窗] > [端點總管]

  • 在 [csDIRegistrationDifferent.http] 的視窗內找到 [verifyregistrationdi] 節點

  • 點選該節點上方的 [傳送要求] 按鈕,此時,將會呼叫此 API 服務端點 

  • 當螢幕上顯示出了服務端點的回應結果,表示這個 API 服務已經成功執行 

  • 在輸出視窗內,將會看到如下的訊息

=== [From VerifyRegistrationDIController ] ======================
Transient Value: 6478
Scoped Value: 5987
Singleton Value: 4042
=================================================================



===  [From TogetherMyService ] ======================
TogetherMyService GetHashCode: 33771145
=====================================================
Transient GetHashCode: 41572837
Transient Value: 4203
Transient服務定位器 Value: 5286
Transient Create Time: 2025/1/13 下午 01:38:20
---------------------------------
Scoped GetHashCode: 61799993
Scoped Value: 5987
Scoped服務定位器 Value: 5987
Scoped Create Time: 2025/1/13 下午 01:38:20
---------------------------------
Singleton GetHashCode: 4898379
Singleton Value: 4042
Singleto服務定位器n Value: 4042
Singleton Create Time: 2025/1/13 下午 01:38:20
  • 再次執行[verifyregistrationdi] 節點上方的 [傳送要求] 按鈕,此時,將會再次呼叫此 API 服務端點

  • 在輸出視窗內,將會看到如下的訊息

=== [From VerifyRegistrationDIController ] ======================
Transient Value: 6446
Scoped Value: 4577
Singleton Value: 4042
=================================================================



===  [From TogetherMyService ] ======================
TogetherMyService GetHashCode: 3908833
=====================================================
Transient GetHashCode: 48180023
Transient Value: 4119
Transient服務定位器 Value: 8605
Transient Create Time: 2025/1/13 下午 01:41:06
---------------------------------
Scoped GetHashCode: 10267778
Scoped Value: 4577
Scoped服務定位器 Value: 4577
Scoped Create Time: 2025/1/13 下午 01:41:06
---------------------------------
Singleton GetHashCode: 4898379
Singleton Value: 4042
Singleto服務定位器n Value: 4042
Singleton Create Time: 2025/1/13 下午 01:38:20
  • 現在來比較與說明這兩次的執行結果

根據輸出結果,我們可以清楚看到三種不同註冊模式的特性。

註冊模式GetHashCode 比較Value 值比較創建時間特性說明
Transient兩次請求的 HashCode 不同:
41572837 → 48180023
每次值都不同:
6478 → 6446
4203 → 4119
5286 → 8605
每次請求都會重新建立:
01:38:20
01:41:06
- 每次注入都是新實例
- 即使在同一個請求中也會創建新的實例
- 最不節省資源但隔離性最好
Scoped兩次請求的 HashCode 不同:
61799993 → 10267778
同一請求中值相同,不同請求值不同:
5987 → 4577
每次請求都會重新建立:
01:38:20
01:41:06
- 同一個請求中共用同一個實例
- 不同請求會創建新的實例
- 適合大多數業務場景
Singleton兩次請求的 HashCode 相同:
4898379 → 4898379
所有請求值都相同:
4042 → 4042
只在第一次建立:
01:38:20
- 整個應用程式只有一個實例
- 所有請求共用同一個實例
- 最節省資源但要注意線程安全

從這個比較可以看出:

  1. Transient 服務每次注入都是全新的實例,適合無狀態且需要完全隔離的服務
  2. Scoped 服務在同一個請求(Request)範圍內是相同實例,適合大多數業務場景
  3. Singleton 服務在整個應用程式生命週期中只有一個實例,適合共用的工具類服務

需要注意的是,選擇哪種生命週期模式要根據實際業務需求來決定,同時要考慮到性能和線程安全的問題。