.NET 8.0 已於 11/14 發佈,.NET 8 為 LTS (長期支援版,單數版號如 .NET 7 為 STS,支援週期只有 18 個月 ) 較符合企業應用需求。最近便試著將 .NET 6.0 專案升到 .NET 8.0,原以為可無痛升級,但連續踩坑搞到灰頭土臉,這裡先分享多建構式型別的 DI 冷門問題一枚。

我的專案有個型別有多個建構式,在使用 DI (依賴注入) 時,DI 服務會不知道使用哪個建構式,這個議題之前研究過,簡單做法是在預期 DI 採用的建構式加上 [ActivatorUtilitiesConstructor],註冊物件時寫成 builder.Services.AddSingleton<MyService>(sp => ActivatorUtilities.CreateInstance<MyService>(sp));

這個做法在 .NET 6 運行多時,試著將專案升級到 .NET 8 後,它開了第一槍,冒出 'Multiple constructors for type 'MyService' were found with length 2.' 錯誤:

我寫了一段小程式重現問題:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<Foo>();
builder.Services.AddSingleton<Bar>();
builder.Services.AddSingleton<MyService>(sp => ActivatorUtilities.CreateInstance<MyService>(sp));

var app = builder.Build();
Console.WriteLine($".NET {Environment.Version}");
try
{
    var scope = app.Services.CreateScope();
    var svc = scope.ServiceProvider.GetRequiredService<MyService>();
    Console.WriteLine("MyService created successfully");
}
catch (Exception ex)
{
    Console.WriteLine("MyService failed to create");
    Console.WriteLine(ex.Message);
}

public class Foo { }
public class Bar { }
public class MyService
{
    [ActivatorUtilitiesConstructor]
    public MyService(Foo foo, IWebHostEnvironment env) { }
    public MyService(Bar bar, IWebHostEnvironment env) { }
}

將 csproj <TargetFramework>net8.0</TargetFramework> 改成 6.0 或 7.0 都能正常執行,切到 8.0 才會出錯。

追了一下原始碼找到問題根源,.NET 8 為了改善 ActivatorUtilities.CreateInstance() 效能,合併了一個修改,原本 bool isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorActtribute)) 的檢查被移除,導致行為改變。

在 .NET Runtime 發了 Issue,得到回應是這屬於 .NET 8 的行為變更(Breaking Change),ActivatorUtilities.CreateInstance() 改為會尋找參數最多的建構式,若有個數相同的建構式便會發生錯誤,而 ActivatorUtilitiesConstructorActtribute 已失去其原有作用。

在上述程式為第一個建構式多加參數,即可引導 ActivatorUtilities.CreateInstance() 使用它建構物件:

增加第二個建構式參數則會改用其建立物件,證明 [ActivatorUtilitiesConstructor] 已無作用。

若專案原本依賴 ActivatorUtilitiesConstructorActtribute 決定建構式,升級 .NET 8 需要進行調整。

ActivatorUtilitiesConstructorAttribute can help ActivatorUtilities.CreateInstance() to choose the preferred constructor of mutilple-contructor type. It works fine in .NET 6/7, and failed in .NET 8.


Comments

Be the first to post a comment

Post a comment