類別 AssemblyLoadContext 是在 .NET Core 中引進的,無法在 .NET Framework 中使用。 本文藉由提供概念性資訊來補充 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 只支援覆寫事件。
Managed 元件載入演算法、附屬元件載入演算法和 Unmanaged(原生)函式庫載入演算法等文章中,會參考所有可用的事件和虛擬函數。 這些文章會顯示載入演算法中每個事件和函式的相對位置。 本文不會重現該資訊。
本節涵蓋相關事件和功能的一般原則。
- 可重複。 特定相依性查詢一律會產生相同的回應。 必須返回相同的已載入的相依實例。 這項需求對於快取一致性而言是基本的。 特別是針對受控組件,我們正在建立 Assembly 快取。 快取索引鍵是簡單的元件名稱。 AssemblyName.Name
-
通常不會丟。 預期這些函式在找不到要求的相依性時會傳回
null,而不是擲回。 擲回會過早結束搜尋,並將例外狀況傳播給呼叫端。 應該僅在意外錯誤情況下拋出,例如損毀的程式組件或記憶體不足。 - 避免遞歸。 請注意,這些函式和處理程式會實作載入規則來尋找相依性。 您的實作不應該呼叫觸發遞歸的 API。 您的程式代碼通常應該呼叫需要特定路徑或記憶體參考自變數的 AssemblyLoadContext 載入函式。
-
載入正確的 AssemblyLoadContext。 載入相依性的位置選擇是應用程式特定的。 選擇是由這些事件和函式實作。 當您的程式代碼呼叫 AssemblyLoadContext 載入路徑函式時,請在您想要載入程式碼的實例上呼叫它們。 有時返回
null並讓AssemblyLoadContext.Default 承擔負擔可能是最簡單的選項。 - 請注意線程競爭。 載入可由多個線程觸發。 AssemblyLoadContext 會藉由原子性地將元件新增至其快取來處理競吋衝突。 競爭失敗者的實例會被捨棄。 在您的實作邏輯中,請勿新增無法正確處理多個線程的額外邏輯。
動態相依性如何隔離?
每個AssemblyLoadContext實例都代表Assembly實例和Type定義的唯一範圍。
這些相依性之間沒有二進位隔離。 他們只因未能以名字互相識別而感到孤立。
在每個 AssemblyLoadContext中:
- AssemblyName.Name 可能會參考不同的 Assembly 實例。
-
Type.GetType 可能會針對相同型別傳回不同的型別實例
name。
共享依賴性
AssemblyLoadContext 實例之間可以輕鬆地共用依賴性。 通常的模型是讓其中一個 AssemblyLoadContext 載入依賴項。 另一個會使用所載入元件的參考來共用相依性。
此共用是運行時間組件所必需的。 這些元件只能載入至AssemblyLoadContext.Default。 像ASP.NET、WPF或WinForms這樣的框架也需要相同的。
建議將共用相依性載入AssemblyLoadContext.Default 此共用是常見的設計模式。
共用是在自定義 AssemblyLoadContext 實例的編碼中實作。 AssemblyLoadContext 具有可覆寫的各種事件和虛擬函式。 當任何這些函式傳回在另一個Assembly實例中載入的AssemblyLoadContext實例的參考時,該Assembly實例會被共用。 標準載入算法會將載入工作推遲到 AssemblyLoadContext.Default,以簡化常見的共享模式。 如需詳細資訊,請參閱 Managed 組件載入演算法。
類型轉換問題
當兩 AssemblyLoadContext 個實例包含具有相同 name的類型定義時,它們不是相同的類型。 只有在它們來自相同實例時,它們才會是相同的 Assembly 類型。
為了使問題複雜化,這些不相符類型的例外狀況訊息可能會造成混淆。 這些類型會依其簡單類型名稱在例外狀況訊息中參考。 在此情況下,常見的例外狀況訊息的格式如下:
類型為 'IsolatedType' 的對象無法轉換成類型 'IsolatedType'。
偵錯類型轉換問題
假設有一對不相符的類型,請務必也知道:
- 每個類型的 Type.Assembly。
- 每個型別的 AssemblyLoadContext,可透過 AssemblyLoadContext.GetLoadContext(Assembly) 函式取得。
假設有兩個物件 a 和 b,在除錯器中評估下列事項會有所幫助:
// 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)
解決類型轉換問題
有兩種設計模式可解決這些類型轉換問題。