備註
本文專屬於 .NET Framework。 它不適用於較新的 .NET 實作,包括 .NET 6 和更新版本。
本文討論如何避免由類型識別問題所引發的 InvalidCastException、MissingMethodException及其他錯誤。 本文討論下列建議:
第一個建議, 瞭解負載內容的優點和缺點,提供其他建議的背景資訊,因為它們全都取決於負載內容的知識。
瞭解負載上下文的優缺點
在應用程式域中,元件可以載入三個上下文中的其中之一,或者可以不使用上下文。
默認載入內容包含通過探查全域組件快取、如果運行時間是在主機上裝載(例如在 SQL Server 中),以及應用程式域的ApplicationBase和PrivateBinPath主機組件存放區來找到的組件。 大多數 Load 方法的多載會將元件載入至此上下文。
載入來源內容包含從載入器未搜尋之位置載入的元件。 例如,外掛程式可能會安裝在應用程式路徑之外的目錄中。 Assembly.LoadFrom、 AppDomain.CreateInstanceFrom和 AppDomain.ExecuteAssembly 是依路徑載入的方法範例。
僅限反映的內容包含以 ReflectionOnlyLoad 和 ReflectionOnlyLoadFrom 方法載入的元件。 無法執行此內容中的程式代碼,因此此處不會討論。 如需詳細資訊,請參閱 如何:將程式集載入 Reflection-Only 環境。
如果您使用反射發射技術來產生暫時性動態程序集,則該程序集不處於任何上下文中。 此外,大多數使用 LoadFile 方法加載的元件都會在沒有上下文的情況下加載,而且從位元組陣列載入的元件也會在沒有上下文的情況下加載,除非其身份識別(在應用策略後)確定它們存在於全域組件快取中。
執行內容有優點和缺點,如下列各節所述。
預設載入環境
當元件載入預設載入內容時,會自動載入其相依性。 載入到預設載入內容的相依性會自動找到存在於預設載入內容或載入-自載入內容中的元件。 元件身分識別的載入可以透過確保不使用未知版本的元件來增強應用程式的穩定性(請參閱 避免僅依靠部分元件名稱進行繫結 一節)。
使用預設載入內容有下列缺點:
載入其他內容的依存關係不可用。
您無法將元件從探查路徑外部的位置載入至預設載入情境。
Load-From 內容
從載入語境(load-from context)允許您從一個不在應用程式路徑內的路徑載入組件,因此不會被包含在探查過程中。 它可讓相依性從該路徑找到並載入,因為路徑資訊是由內容維護。 此外,在此背景下的程序集可以使用載入至預設載入內容的相依項。
使用 Assembly.LoadFrom 方法載入元件,或依路徑載入的其中一個其他方法,有下列缺點:
如果已從內容載入具有相同身分識別的元件,即使指定了不同的路徑, LoadFrom 仍會傳回載入的元件。
如果元件是使用 LoadFrom載入 ,且稍後預設載入內容中的元件會嘗試依顯示名稱載入相同的元件,載入嘗試就會失敗。 反序列化組件時,可能會發生這種情況。
如果元件是使用LoadFrom載入的,且探查路徑包含具有相同身分識別但位於不同位置的元件,則可能發生InvalidCastException、MissingMethodException或其他非預期行為。
LoadFrom要求FileIOPermissionAccess.Read和FileIOPermissionAccess.PathDiscovery,或者WebPermission,在指定的路徑上。
如果有原生映像存在於此元件,則不使用它。
組件無法載入為不具網域專屬性。
在 .NET Framework 1.0 和 1.1 版中,不會套用原則。
沒有內容
不提供上下文載入是使用反射式發射產生瞬態組件的唯一選項。 在無上下文的情況下載入是將多個具有同一識別的元件載入一個應用程式域的唯一方法。 探查成本已被避免。
除非套用原則時所建立的組件符合全域組件快取中組件的身分識別,否則從位元組陣列載入的組件會在沒有環境的情況下載入;在這種情況下,組件會從全域組件快取中載入。
在沒有上下文的情況下載入組件有以下缺點:
除非您處理 AppDomain.AssemblyResolve 事件,否則其他組件無法繫結至未具上下文載入的組件。
相依性不會自動載入。 您可以在沒有內容的情況下預先載入它們、將它們預先載入至預設載入內容,或藉由處理 AppDomain.AssemblyResolve 事件來載入它們。
在沒有上下文的情況下載入具有相同識別的多個元件,可能會導致類型識別問題,類似於將具有相同識別的元件載入多個上下文所造成的類型識別問題。 請參閱 避免在多個內容中載入元件。
如果有原生映像存在於此元件,則不使用它。
組件無法載入為不具網域專屬性。
在 .NET Framework 1.0 和 1.1 版中,不會套用原則。
避免部分元件名稱上的系結
當您只指定程式集顯示名稱的一部分FullName時,載入元件時就會發生部分名稱系結。 例如,您可以只使用元件的簡單名稱來呼叫 Assembly.Load 方法,省略版本、文化特性和公鑰令牌。 或者,您可以呼叫 Assembly.LoadWithPartialName 方法,其會首先呼叫 Assembly.Load 方法,如果仍無法找到元件,會搜尋全域組件快取並載入元件的最新可用版本。
部分名稱系結可能會導致許多問題,包括:
Assembly.LoadWithPartialName方法可能會載入具有相同簡單名稱的不同元件。 例如,兩個應用程式可能會將兩個具有簡單名稱
GraphicsLibrary的完全不同的元件安裝到全域程式集緩存。實際載入的元件可能無法回溯相容。 例如,若未指定版本,可能會導致載入比您程式原本撰寫使用的版本還要晚的版本。 更新版本中的變更可能會導致應用程式中的錯誤。
實際載入的元件可能無法與未來版本相容。 例如,您可能已使用最新版本的元件來建置及測試應用程式,但部分系結可能會載入較舊版本,而缺少應用程式使用的功能。
安裝新的應用程式可能會中斷現有的應用程式。 使用 LoadWithPartialName 方法的應用程式可以藉由安裝較新的不相容共用元件版本來中斷。
可能發生非預期的相依性載入。 如果您載入了兩個共用相依性的組件,以部分繫結的方式載入它們,可能會導致其中一個組件使用了未經建置或測試的元件。
因為問題可能會造成,所以 LoadWithPartialName 方法已標示為過時。 建議您改用 Assembly.Load 方法,並指定完整的元件顯示名稱。 請參閱 瞭解負載內容的優點和缺點 , 並考慮切換至預設載入內容。
如果您想要使用 LoadWithPartialName 方法,因為它會使元件載入變得容易,請考慮讓您的應用程式因錯誤訊息而失敗,其中指出遺漏的元件,這可能會比自動使用未知版本的元件提供更好的用戶體驗,因為未知版本可能會導致無法預期的行為和安全性漏洞。
避免將組件載入多個上下文
將組件載入多個環境可能會導致類型識別問題。 如果相同的類型從相同的元件載入到兩個不同的內容中,就好像已載入具有相同名稱的兩個不同類型一樣。 如果您嘗試將型別InvalidCastException轉換成型別MyType,則會擲回MyType,並顯示型別InvalidCastException無法轉換成型別MyType的混淆訊息。
例如,假設 ICommunicate 介面是在名為 Utility 的元件中宣告,而這個元件不僅由您的程式引用,也被程式所載入的其他元件引用。 這些其他元件包含實作 ICommunicate 介面的類型,可讓您的程式使用這些類型。
現在,請考慮執行程式時會發生什麼事。 程式所參考的程式庫會載入至預設載入環境。 如果您使用 Load 方法,依其身分識別載入目標元件,這樣會在預設載入內容中,其相依性也是如此。 您的程式和目標元件都會使用相同的 Utility 元件。
不過,假設您使用 LoadFile 方法,依其檔案路徑載入目標元件。 元件載入時沒有任何內容,因此不會自動載入其相依性。 您可能有一個針對AppDomain.AssemblyResolve事件的處理程式,以提供依賴項,並且它可能會使用 Utility 方法在沒有上下文的情況下載入LoadFile元件。 現在,當您建立包含在目標組件中的類型實例並嘗試將其指派給ICommunicate類型的變數時,就會拋出InvalidCastException,因為運行時會將ICommunicate組件的兩個Utility複本中的介面視為不同的類型。
有許多其他情境可以將元件載入多個上下文。 最佳方法是將目標元件重新放置到應用程式路徑中,並使用 Load 具有完整顯示名稱的方法,以避免衝突。 然後,程序集會載入到預設載入上下文中,而且兩個程序集都使用相同的 Utility 程序集。
如果目標元件必須保留在應用程式路徑之外,您可以使用 LoadFrom 方法將它載入至載入自上下文。 如果目標元件是以應用程式所參考的 Utility 元件進行編譯,它會使用已載入到應用程式預設載入環境中的 Utility 元件。 請注意,如果目標元件相依於位於應用程式路徑外部之 Utility 元件的複本,就可能發生問題。 如果在你的應用程式載入 Utility 元件之前,該元件已經載入到「載入來源」內容中,那麼你的應用程式載入將會失敗。
考慮切換至預設載入內容一節討論使用檔案路徑載入的替代方案,例如 LoadFile 和 LoadFrom。
避免將多個版本的元件載入相同的上下文
將多個版本的元件載入至同一載入上下文中,可能會導致類型身分識別問題。 如果相同類型是從相同元件的兩個版本載入,就好像已載入具有相同名稱的兩個不同類型一樣。 如果您嘗試將型別InvalidCastException轉換成型別MyType,則會擲回MyType,並顯示型別InvalidCastException無法轉換成型別MyType的混淆訊息。
例如,您的程式可能會直接載入某個版本的 Utility 元件,稍後可能會載入另一個元件,該元件載入不同版本的 Utility 元件。 或者,程式碼錯誤可能會導致應用程式中的兩個不同的程式碼路徑載入不同版本的程序集。
在預設載入內容中,當您使用 Assembly.Load 方法並指定包含不同版本號碼的完整元件顯示名稱時,就會發生此問題。 對於沒有內容載入的元件,問題可能是使用 Assembly.LoadFile 方法來從不同的路徑載入相同的元件所造成。 運行時間會將從不同路徑載入的兩個元件視為不同的元件,即使其身分識別相同也一樣。
除了類型識別問題之外,如果從某個版本的元件載入的類型傳遞至預期來自不同版本之類型的程式代碼,則多個版本的元件可能會造成 MissingMethodException 。 例如,程式代碼可能會預期已新增至更新版本的方法。
如果類型的行為在版本之間變更,就會發生更細微的錯誤。 例如,方法可能會擲回非預期的例外狀況,或傳回非預期的值。
請仔細檢閱您的程式碼,以確保只載入一個組件的版本。 您可以使用 AppDomain.GetAssemblies 方法來判斷隨時載入的組件。
請考慮切換至預設載入情境
檢查應用程式的元件載入和部署模式。 您可以消除從位元組陣列載入的組件嗎? 您可以將元件移至探查路徑嗎? 如果元件位於全域程式集緩存中,或位於應用程式域探查路徑中(也就是其 ApplicationBase 和 PrivateBinPath),您可以依其身分識別載入元件。
如果無法將所有元件放在探查路徑中,請考慮使用 .NET Framework 載入巨集模型、將元件放入全域程式集緩存或建立應用程式域等替代方案。
請考慮使用 .NET Framework Add-In 模型
如果您使用 Load-from 內容來實作通常未安裝在應用程式基底中的附加元件,請使用 .NET Framework 附加元件模型。 此模型會在應用程式域或進程層級提供隔離,而不需要您自行管理應用程式域。 如需外掛程式模型的相關資訊,請參閱外掛程式和擴充性。
請考慮使用全域程式集緩存
將元件放入全域元件快取中,以獲得應用程式基底外的共享元件路徑優勢,同時不失去預設載入內容的好處,也不會遭遇其他載入內容的缺點。
請考慮使用應用程式域
如果您判斷某些元件無法部署在應用程式的探查路徑中,請考慮為這些元件建立新的應用程式域。 使用 AppDomainSetup 來建立新的應用程式域,並使用 AppDomainSetup.ApplicationBase 屬性來指定包含您要載入的組件之路徑。 如果您有多個要探查的目錄,您可以將 設定 ApplicationBase 為根目錄,並使用 AppDomainSetup.PrivateBinPath 屬性來識別要探查的子目錄。 或者,您可以建立多個應用程式域,並設定每個應用程式域的 ApplicationBase 為其元件的適當路徑。
請注意,您可以使用 Assembly.LoadFrom 方法來載入這些元件。 因為它們現在位於探查路徑中,因此會載入預設載入上下文,而不是從上下文載入。 不過,我們建議您切換至 Assembly.Load 方法,並提供完整的元件顯示名稱,以確保一律使用正確的版本。