關於 System.Runtime.Loader.AssemblyLoadContext

AssemblyLoadContext 類別是在 .NET Core 中引進,且無法在 .NET Framework 中使用。 此文章以概念資訊補充 AssemblyLoadContext API 文件。

此文章與實作動態載入的開發人員有關,特別是動態載入架構開發人員。

什麼是 AssemblyLoadContext?

每個 .NET 5+ 和 .NET Core 應用程式都會隱含使用 AssemblyLoadContext。 其為執行階段的提供者,用於尋找和載入相依性。 每當載入相依性時,就會叫用 AssemblyLoadContext 執行個體來尋找該相依性。

  • AssemblyLoadContext 提供尋找、載入及快取受控組件與其他相依性的服務。
  • 為了支援動態程式碼載入和卸載,其會建立隔離的內容,以在其自己的 AssemblyLoadContext 執行個體中載入程式碼與其相依性。

版本設定規則

單一 AssemblyLoadContext 執行個體僅限於針對每個簡單組件名稱只載入一個 Assembly 版本。 針對已經載入該名稱之組件的 AssemblyLoadContext 執行個體解析組件參考時,系統會比較要求的版本與載入的版本。 只有在載入的版本等於或高於要求的版本時,解析才會成功。

何時需要多個 AssemblyLoadContext 執行個體?

單一 AssemblyLoadContext 執行個體只能載入一個組件版本的限制,在動態載入程式碼模組時可能會造成問題。 每個模組都會獨立編譯,而且模組可能相依於不同版本的 Assembly。 當不同的模組相依於常用程式庫的不同版本時,這通常會造成問題。

為了支援動態載入程式碼,AssemblyLoadContext API 能提供在相同應用程式中載入衝突之 Assembly 版本的能力。 每個 AssemblyLoadContext 執行個體都會提供唯一的字典,能將每個 AssemblyName.Name 對應至特定的 Assembly 執行個體。

其也提供方便的機制,以便將與程式碼模組相關的相依性組成群組,以供稍後卸載。

AssemblyLoadContext.Default 執行個體

AssemblyLoadContext.Default 執行個體會在啟動時由執行階段自動填入。 其會使用預設探查來尋找所有靜態相依性。

其可解決最常見的相依性載入案例。

動態相依性

AssemblyLoadContext 具有可覆寫的各種事件與虛擬函式。

AssemblyLoadContext.Default 執行個體只支援覆寫事件。

受控組件載入演算法附屬組件載入演算法非受控 (原生) 程式庫載入演算法指的是所有可用的事件與虛擬函式。 這些文章會示範每個事件與函式在載入演算法中的相對位置。 此文章不會重現該資訊。

此節涵蓋相關事件與函式的一般原則。

  • 可重複。 特定相依性的查詢必須一律產生相同的回應。 必須傳回相同的已載入相依性執行個體。 此需求是快取一致性的基礎。 特別是針對受控組件,我們會建立 Assembly 快取。 快取索引鍵是簡單的組件名稱 AssemblyName.Name
  • 通常不會擲回。 預期這些函式會在找不到要求的相依性時傳回 null,而不是擲回。 擲回會提前結束搜尋,並將例外狀況傳播至呼叫者。 擲回應該限制為非預期的錯誤,例如損毀的組件或記憶體不足狀況。
  • 避免遞迴。 請注意,這些函式與處理常式會實作載入規則來尋找相依性。 您的實作不應該呼叫會觸發遞迴的 API。 您的程式碼通常應該呼叫需要特定路徑或記憶體參考引數的 AssemblyLoadContext 載入函式。
  • 載入正確的 AssemblyLoadContext。 要載入相依性的位置選擇是應用程式專有的。 該選擇是由這些事件與函式所實作。 當您的程式碼呼叫 AssemblyLoadContext 時,「依路徑載入」函式會在您想要載入程式碼的執行個體上加以呼叫。 有時傳回 null 並讓 AssemblyLoadContext.Default 處理載入可能是最簡單的選項。
  • 請注意執行緒競爭。 載入可由多個執行緒觸發。 AssemblyLoadContext 會透過以不可部分完成的方式將組件新增至其快取來處理執行緒競爭。 系統會捨棄競爭失敗者的執行個體。 在您的實作邏輯中,請勿新增無法正確處理多個執行緒的額外邏輯。

動態相依性如何隔離?

每個 AssemblyLoadContext 執行個體都代表 Assembly 執行個體與 Type 定義的唯一範圍。

這些相依性之間沒有二進位隔離。 其僅透過無法依名稱找到彼此來隔離。

在每個 AssemblyLoadContext 中:

共用的相依性

AssemblyLoadContext 執行個體之間可以輕鬆地共用相依性。 一般模型適用於讓單一 AssemblyLoadContext 載入一個相依性。 其他則會使用已載入組件的參考來共用相依性。

執行階段組件需要此共用。 這些組件只能載入 AssemblyLoadContext.DefaultASP.NETWPFWinForms 之類的架構也具有相同的需求。

建議將共用相依性載入 AssemblyLoadContext.Default。 此共用是通用的設計模式。

共用是在自訂 AssemblyLoadContext 執行個體的程式碼撰寫作業中實作。 AssemblyLoadContext 具有可覆寫的各種事件與虛擬函式。 當其中任何一個函式傳回在另一個 AssemblyLoadContext 執行個體中載入的 Assembly 執行個體參考時,便會共用 Assembly 執行個體。 標準載入演算法會聽從 AssemblyLoadContext.Default 以進行載入,以簡化通用的共用模式。 如需詳細資訊,請參閱受控組件載入演算法

類型轉換問題

當兩個 AssemblyLoadContext 執行個體包含具有相同 name 的型別定義時,其並非相同的型別。 只有在其來自相同的 Assembly 執行個體時,其才是相同的型別。

使問題更加複雜的是,關於這些不相符型別的例外狀況訊息可能會造成混淆。 在例外狀況訊息中,會以型別的簡單型別名稱來參考這些類型。 在此情況下,常見的例外狀況訊息的格式如下:

'IsolatedType' 型別的物件無法轉換成 'IsolatedType' 型別。

針對型別轉換問題進行偵錯

假設有一對不相符的型別,也請務必知道:

假設有兩個物件 (ab),在偵錯工具中評估下列項目會很有幫助:

// In debugger look at each assembly's instance, Location, and FullName
a.GetType().Assembly
b.GetType().Assembly
// In debugger look at each AssemblyLoadContext's instance and name
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(a.GetType().Assembly)
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(b.GetType().Assembly)

解決型別轉換問題

有兩種設計模式可解決這些型別轉換問題。

  1. 使用通用的共用型別。 這個共用型別可以是基本執行階段型別,或可以涉及在共用組件中建立新的共用型別。 共用型別通常是在應用程式組件中定義的介面。 如需詳細資訊,請參閱相依性的共用方式

  2. 使用封送處理技術,將某個型別轉換成另一種型別。