組件載入的最佳作法Best Practices for Assembly Loading

本文討論如何避免發生可能造成 InvalidCastExceptionMissingMethodException 和其他錯誤之類型身分識別的問題。This article discusses ways to avoid problems of type identity that can lead to InvalidCastException, MissingMethodException, and other errors. 本文討論下列建議:The article discusses the following recommendations:

第一個建議 (了解載入內容的優缺點) 提供其他建議的背景資訊,因為它們都取決於載入內容的知識。The first recommendation, understand the advantages and disadvantages of load contexts, provides background information for the other recommendations, because they all depend on a knowledge of load contexts.

了解載入內容的優缺點Understand the Advantages and Disadvantages of Load Contexts

在應用程式定義域內,可以將組件載入三個內容之一,或是載入時沒有內容:Within an application domain, assemblies can be loaded into one of three contexts, or they can be loaded without context:

  • 預設載入內容包含探查全域組件快取時所找到的組件、裝載執行階段時的主機組件存放區 (例如,在 SQL Server 中),以及應用程式定義域的 ApplicationBasePrivateBinPathThe default load context contains assemblies found by probing the global assembly cache, the host assembly store if the runtime is hosted (for example, in SQL Server), and the ApplicationBase and PrivateBinPath of the application domain. Load 方法的大部分多載都會將組件載入此內容中。Most overloads of the Load method load assemblies into this context.

  • 載入來源內容包含從載入器未搜尋的位置中載入的組件。The load-from context contains assemblies that are loaded from locations that are not searched by the loader. 例如,增益集可能安裝在不在應用程式路徑下的目錄。For example, add-ins might be installed in a directory that is not under the application path. Assembly.LoadFromAppDomain.CreateInstanceFromAppDomain.ExecuteAssembly 是依路徑載入之方法的範例。Assembly.LoadFrom, AppDomain.CreateInstanceFrom, and AppDomain.ExecuteAssembly are examples of methods that load by path.

  • 僅限反映的內容包含使用 ReflectionOnlyLoadReflectionOnlyLoadFrom 方法所載入的組件。The reflection-only context contains assemblies loaded with the ReflectionOnlyLoad and ReflectionOnlyLoadFrom methods. 無法執行此內容中的程式碼,因此此處不予討論。Code in this context cannot be executed, so it is not discussed here. 如需詳細資訊,請參閱如何:將組件載入僅限反映的內容For more information, see How to: Load Assemblies into the Reflection-Only Context.

  • 如果您已使用反映發出來產生暫時性動態組件,則組件不會在任何內容中。If you generated a transient dynamic assembly by using reflection emit, the assembly is not in any context. 此外,使用 LoadFile 方法所載入的大部分組件在載入時都沒有內容,而且從位元組陣列載入的組件在載入時沒有內容,除非它們在套件原則之後所建立的身分識別位於全域組件快取中。In addition, most assemblies that are loaded by using the LoadFile method are loaded without context, and assemblies that are loaded from byte arrays are loaded without context unless their identity (after policy is applied) establishes that they are in the global assembly cache.

執行內容具有優缺點,如下列各節所討論。The execution contexts have advantages and disadvantages, as discussed in the following sections.

預設載入內容Default Load Context

將組件載入預設載入內容時,會自動載入其相依性。When assemblies are loaded into the default load context, their dependencies are loaded automatically. 針對預設載入內容或載入來源內容中的組件,自動找到載入到預設載入內容的相依性。Dependencies that are loaded into the default load context are found automatically for assemblies in the default load context or the load-from context. 藉由確保未使用未知版本的組件,依組件身分識別載入可增加應用程式的穩定性 (請參閱避免部分組件名稱上的繫結節)。Loading by assembly identity increases the stability of applications by ensuring that unknown versions of assemblies are not used (see the Avoid Binding on Partial Assembly Names section).

使用預設載入內容的缺點如下:Using the default load context has the following disadvantages:

  • 無法使用載入至其他內容的相依性。Dependencies that are loaded into other contexts are not available.

  • 您無法將組件從探查路徑外部的位置載入預設載入內容。You cannot load assemblies from locations outside the probing path into the default load context.

載入來源內容Load-From Context

載入來源內容可讓您從不在應用程式路徑下的路徑中載入組件,因此不會納入探查。The load-from context lets you load an assembly from a path that is not under the application path, and therefore is not included in probing. 它可以從該路徑尋找和載入相依性,因為是透過內容來維護路徑資訊。It enables dependencies to be located and loaded from that path, because the path information is maintained by the context. 此外,此內容中的組件可以使用載入預設載入內容的相依性。In addition, assemblies in this context can use dependencies that are loaded into the default load context.

使用 Assembly.LoadFrom 方法或依路徑載入的其他一個方法來載入組件,其缺點如下:Loading assemblies by using the Assembly.LoadFrom method, or one of the other methods that load by path, has the following disadvantages:

  • 如果已載入具有相同身分識別的組件,LoadFrom 會傳回載入的組件,即使指定不同的路徑也是一樣。If an assembly with the same identity is already loaded, LoadFrom returns the loaded assembly even if a different path was specified.

  • 如果使用 LoadFrom 載入組件,之後預設載入內容中的組件卻嘗試依顯示名稱載入相同組件,則載入嘗試會失敗。If an assembly is loaded with LoadFrom, and later an assembly in the default load context tries to load the same assembly by display name, the load attempt fails. 還原序列化組件時,也可能發生這種情況。This can occur when an assembly is deserialized.

  • 如果使用 LoadFrom 載入組件,而且探查路徑包括具有相同身分識別但在不同位置的組件,則可能會發生 InvalidCastExceptionMissingMethodException 或其他非預期的行為。If an assembly is loaded with LoadFrom, and the probing path includes an assembly with the same identity but in a different location, an InvalidCastException, MissingMethodException, or other unexpected behavior can occur.

  • LoadFrom 要求所指定路徑上的 FileIOPermissionAccess.ReadFileIOPermissionAccess.PathDiscoveryWebPermissionLoadFrom demands FileIOPermissionAccess.Read and FileIOPermissionAccess.PathDiscovery, or WebPermission, on the specified path.

  • 如果組件有原生映像,則不會使用它。If a native image exists for the assembly, it is not used.

  • 組件無法以定義域中性方式載入。The assembly cannot be loaded as domain-neutral.

  • 在 .NET Framework 1.0 和 1.1 版中,不會套用原則。In the .NET Framework versions 1.0 and 1.1, policy is not applied.

沒有內容No Context

沒有內容的載入是使用反映發出所產生之暫時性組件的唯一選項。Loading without context is the only option for transient assemblies that are generated with reflection emit. 沒有內容的載入是將多個具有相同身分識別的組件載入到一個應用程式定義域的唯一方法。Loading without context is the only way to load multiple assemblies that have the same identity into one application domain. 會避免發生探查成本。The cost of probing is avoided.

從位元組陣列載入的組件在載入時沒有內容,除非在套用規則時所建立之組件的身分識別符合全域組件快取中組件的身分識別;在該情況下,會從全域組件快取中載入組件。Assemblies that are loaded from byte arrays are loaded without context unless the identity of the assembly, which is established when policy is applied, matches the identity of an assembly in the global assembly cache; in that case, the assembly is loaded from the global assembly cache.

載入沒有內容之組件的缺點如下:Loading assemblies without context has the following disadvantages:

  • 除非您處理 AppDomain.AssemblyResolve 事件,否則其他組件無法繫結至載入時沒有內容的組件。Other assemblies cannot bind to assemblies that are loaded without context, unless you handle the AppDomain.AssemblyResolve event.

  • 不會自動載入相依性。Dependencies are not loaded automatically. 您可以預先載入它們但沒有內容、將它們預先載入至預設載入內容,或者處理 AppDomain.AssemblyResolve 事件來載入它們。You can preload them without context, preload them into the default load context, or load them by handling the AppDomain.AssemblyResolve event.

  • 載入具有相同身分識別但沒有內容的多個組件可能會造成類型身分識別問題,而這些問題與將具有相同身分識別的組件載入多個內容所造成的問題類似。Loading multiple assemblies with the same identity without context can cause type identity problems similar to those caused by loading assemblies with the same identity into multiple contexts. 請參閱避免將組件載入多個內容See Avoid Loading an Assembly into Multiple Contexts.

  • 如果組件有原生映像,則不會使用它。If a native image exists for the assembly, it is not used.

  • 組件無法以定義域中性方式載入。The assembly cannot be loaded as domain-neutral.

  • 在 .NET Framework 1.0 和 1.1 版中,不會套用原則。In the .NET Framework versions 1.0 and 1.1, policy is not applied.

避免部分組件名稱上的繫結Avoid Binding on Partial Assembly Names

如果您在載入組件時指定組件顯示名稱的唯一部分,就會發生部分名稱繫結 (FullName)。Partial name binding occurs when you specify only part of the assembly display name (FullName) when you load an assembly. 例如,您可能會呼叫只具有組件簡單名稱的 Assembly.Load 方法,並省略版本、文化特性和公開金鑰權杖。For example, you might call the Assembly.Load method with only the simple name of the assembly, omitting the version, culture, and public key token. 或者,您可能會呼叫 Assembly.LoadWithPartialName 方法,此方法會先呼叫 Assembly.Load 方法,並在找不到組件時搜尋全域組件快取,並載入組件的最新可用版本。Or you might call the Assembly.LoadWithPartialName method, which first calls the Assembly.Load method and, if that fails to locate the assembly, searches the global assembly cache and loads the latest available version of the assembly.

部分名稱繫結會造成許多問題,包括下列:Partial name binding can cause many problems, including the following:

  • Assembly.LoadWithPartialName 方法可能會載入具有相同簡單名稱的不同組件。The Assembly.LoadWithPartialName method might load a different assembly with the same simple name. 例如,兩個應用程式可能會將兩個具有簡單名稱 GraphicsLibrary 的完全不同組件安裝到全域組件快取中。For example, two applications might install two completely different assemblies that both have the simple name GraphicsLibrary into the global assembly cache.

  • 實際載入的組件可能無法與舊版相容。The assembly that is actually loaded might not be backward-compatible. 例如,未指定版本可能會導致載入的版本,比一開始撰寫成使用之程式的版本還會新。For example, not specifying the version might result in the loading of a much later version than the version your program was originally written to use. 更新版本中的變更可能導致應用程式發生錯誤。Changes in the later version might cause errors in your application.

  • 實際載入的組件可能不正向相容。The assembly that is actually loaded might not be forward-compatible. 例如,您可能已使用最新版本的組件來建置並測試應用程式,但部分繫結可能會載入缺乏應用程式所使用功能的更早版本。For example, you might have built and tested your application with the latest version of an assembly, but partial binding might load a much earlier version that lacks features your application uses.

  • 安裝新的應用程式可能會中斷現有應用程式。Installing new applications can break existing applications. 安裝更新且不相容版本的共用組件,可能會中斷使用 LoadWithPartialName 方法的應用程式。An application that uses the LoadWithPartialName method can be broken by installing a newer, incompatible version of a shared assembly.

  • 可能會載入非預期的相依性。Unexpected dependency loading can occur. 如果您載入兩個共用相依性的組件,則使用部分繫結載入它們可能會導致一個組件,而此組件使用未用來建置或測試它的元件。It you load two assemblies that share a dependency, loading them with partial binding might result in one assembly using a component that it was not built or tested with.

LoadWithPartialName 方法可能會導致問題,因此已標示為過時。Because of the problems it can cause, the LoadWithPartialName method has been marked obsolete. 建議您改用 Assembly.Load 方法,並指定完整組件顯示名稱。We recommend that you use the Assembly.Load method instead, and specify full assembly display names. 請參閱了解載入內容的優缺點考慮切換成預設載入內容See Understand the Advantages and Disadvantages of Load Contexts and Consider Switching to the Default Load Context.

如果您因可以更輕鬆地載入組件而想要使用 LoadWithPartialName 方法,請考慮讓應用程式失敗時具有識別遺漏組件的錯誤訊息,而其提供的使用者體驗可能優於自動使用未知版本的組件,這可能會造成無法預期的行為和安全性漏洞。If you want to use the LoadWithPartialName method because it makes assembly loading easy, consider that having your application fail with an error message that identifies the missing assembly is likely to provide a better user experience than automatically using an unknown version of the assembly, which might cause unpredictable behavior and security holes.

避免將組件載入多個內容Avoid Loading an Assembly into Multiple Contexts

將載入組件多個內容,可能會造成類型身分識別問題。Loading an assembly into multiple contexts can cause type identity problems. 如果將相同類型從相同的組件載入兩個不同的內容,就像已載入具有相同名稱的兩個不同類型一樣。If the same type is loaded from the same assembly into two different contexts, it is as if two different types with the same name had been loaded. 如果您嘗試將某種類型轉換為另一種類型,則會擲回 InvalidCastException,以及讓人混淆的訊息:無法將 MyType 類型轉換為 MyType 類型。An InvalidCastException is thrown if you try to cast one type to the other, with the confusing message that type MyType cannot be cast to type MyType.

例如,假設在名為 Utility 的組件中宣告 ICommunicate 介面,而您的程式和您程式所載入的其他組件都參考此組件。For example, suppose that the ICommunicate interface is declared in an assembly named Utility, which is referenced by your program and also by other assemblies that your program loads. 這些其他組件包含實作 ICommunicate 介面的類型,讓您的程式可以使用它們。These other assemblies contain types that implement the ICommunicate interface, allowing your program to use them.

現在,請考慮您的程式執行時會發生什麼事。Now consider what happens when your program is run. 您程式所參考的組件會載入預設載入內容。Assemblies that are referenced by your program are loaded into the default load context. 如果您是依身分識別來載入目標組件,並使用 Load 方法,則該組件和其相依性都會在預設載入內容中。If you load a target assembly by its identity, using the Load method, it will be in the default load context, and so will its dependencies. 您的程式和目標組件都會使用相同 Utility 組件。Both your program and the target assembly will use the same Utility assembly.

不過,假設您依檔案路徑載入目標組件,並使用 LoadFile 方法。However, suppose you load the target assembly by its file path, using the LoadFile method. 組件在載入時沒有任何內容,因此不會自動載入其相依性。The assembly is loaded without any context, so its dependencies are not automatically loaded. 您可能有 AppDomain.AssemblyResolve 事件的處理常式來提供相依性,而且它可能會使用 LoadFile 方法載入沒有內容的 Utility 組件。You might have a handler for the AppDomain.AssemblyResolve event to supply the dependency, and it might load the Utility assembly with no context by using the LoadFile method. 現在,如果您建立目標組件中所含類型的執行個體,並嘗試將它指派給 ICommunicate 類型的變數,則會擲回 InvalidCastException,因為執行階段會將 Utility 組件之兩個複本中的 ICommunicate 介面視為不同類型。Now when you create an instance of a type that is contained in the target assembly and try to assign it to a variable of type ICommunicate, an InvalidCastException is thrown because the runtime considers the ICommunicate interfaces in the two copies of the Utility assembly to be different types.

有許多其他情況可以將組件載入多個內容。There are many other scenarios in which an assembly can be loaded into multiple contexts. 最佳方式是將目標組件重新放置在應用程式路徑中,並搭配使用 Load 方法與完整顯示名稱,以避免衝突。The best approach is to avoid conflicts by relocating the target assembly in your application path and using the Load method with the full display name. 組件接著會載入預設載入內容,而且兩個組件都使用相同 Utility 組件。The assembly is then loaded into the default load context, and both assemblies use the same Utility assembly.

如果目標組件必須保留在應用程式路徑外部,您可以使用 LoadFrom 方法,以將它載入到載入來源內容。If the target assembly must remain outside your application path, you can use the LoadFrom method to load it into the load-from context. 如果目標組件編譯成具有應用程式 Utility 組件的參考,則會使用應用程式已載入預設載入內容的 Utility 組件。If the target assembly was compiled with a reference to your application's Utility assembly, it will use the Utility assembly that your application has loaded into the default load context. 請注意,如果目標組件相依於位在應用程式路徑外部之 Utility 組件的複本,則可能會發生問題。Note that problems can occur if the target assembly has a dependency on a copy of the Utility assembly located outside your application path. 如果在應用程式載入 Utility 組件之前將該組件載入到載入來源內容,您應用程式的載入將會失敗。If that assembly is loaded into the load-from context before your application loads the Utility assembly, your application's load will fail.

考慮切換成預設載入內容一節討論使用 LoadFileLoadFrom 這類檔案路徑載入的替代方式。The Consider Switching to the Default Load Context section discusses alternatives to using file path loads such as LoadFile and LoadFrom.

避免將多個版本的組件載入相同的內容Avoid Loading Multiple Versions of an Assembly into the Same Context

將組件的多個版本載入一個載入內容可能會造成類型身分識別問題。Loading multiple versions of an assembly into one load context can cause type identity problems. 如果從相同組件的兩個版本載入相同類型,就像已載入具有相同名稱的兩個不同類型一樣。If the same type is loaded from two versions of the same assembly, it is as if two different types with the same name had been loaded. 如果您嘗試將某種類型轉換為另一種類型,則會擲回 InvalidCastException,以及讓人混淆的訊息:無法將 MyType 類型轉換為 MyType 類型。An InvalidCastException is thrown if you try to cast one type to the other, with the confusing message that type MyType cannot be cast to type MyType.

例如,您的程式可能會直接載入 Utility 組件的一個版本,之後可能會載入另一個載入 Utility 組件之不同版本的組件。For example, your program might load one version of the Utility assembly directly, and later it might load another assembly that loads a different version of the Utility assembly. 或者,程式碼錯誤可能會在應用程式中產生兩個不同的程式碼路徑,以載入組件的不同版本。Or a coding error might cause two different code paths in your application to load different versions of an assembly.

在預設載入內容中,如果您使用 Assembly.Load 方法,並指定包括不同版本號碼的完整組件顯示名稱,則會發生此問題。In the default load context, this problem can occur when you use the Assembly.Load method and specify complete assembly display names that include different version numbers. 針對載入時沒有內容的組件,使用 Assembly.LoadFile 方法以從不同路徑載入相同組件時可能會發生問題。For assemblies that are loaded without context, the problem can be caused by using the Assembly.LoadFile method to load the same assembly from different paths. 執行階段會將兩個從不同路徑載入的組件視為不同組件,即使其身分識別相同也是一樣。The runtime considers two assemblies that are loaded from different paths to be different assemblies, even if their identities are the same.

除了類型身分識別問題之外,如果將從某個版本的組件載入的類型傳遞給預期來自不同版本之該類型的程式碼,則組件的多個版本可能還會導致 MissingMethodExceptionIn addition to type identity problems, multiple versions of an assembly can cause a MissingMethodException if a type that is loaded from one version of the assembly is passed to code that expects that type from a different version. 例如,程式碼可能預期已新增至較新版本的方法。For example, the code might expect a method that was added to the later version.

如果版本之間的類型行為變更,可能會發生更細微的錯誤。More subtle errors can occur if the behavior of the type changed between versions. 例如,方法可能會擲回未預期的例外狀況,或傳回未預期的值。For example, a method might throw an unexpected exception or return an unexpected value.

請仔細檢閱您的程式碼,確保只載入組件的一個版本。Carefully review your code to ensure that only one version of an assembly is loaded. 您可以使用 AppDomain.GetAssemblies 方法,判斷在任何指定時間載入的組件。You can use the AppDomain.GetAssemblies method to determine which assemblies are loaded at any given time.

考慮切換成預設載入內容Consider Switching to the Default Load Context

檢查您應用程式的組件載入和部署模式。Examine your application's assembly loading and deployment patterns. 您可以排除從位元組陣列載入的組件嗎?Can you eliminate assemblies that are loaded from byte arrays? 您可以將組件移到探查路徑嗎?Can you move assemblies into the probing path? 如果組件位在全域組件快取或應用程式定義域的探查路徑中 (即其 ApplicationBasePrivateBinPath),您可以依其身分識別來載入組件。If assemblies are located in the global assembly cache or in the application domain's probing path (that is, its ApplicationBase and PrivateBinPath), you can load the assembly by its identity.

如果不可能將您的所有組件放在探查路徑中,請考慮使用替代項目,例如使用 .NET Framework 增益集模型、將組件放入全域組件快取中,或建立應用程式定義域。If it is not possible to put all your assemblies in the probing path, consider alternatives such as using the .NET Framework add-in model, placing assemblies into the global assembly cache, or creating application domains.

考慮使用 .NET Framework 增益集模型Consider Using the .NET Framework Add-In Model

如果您使用載入來源內容來實作通常不會安裝在應用程式基底中的增益集,請使用 .NET Framework 增益集模型。If you are using the load-from context to implement add-ins, which typically are not installed in the application base, use the .NET Framework add-in model. 此模型會提供應用程式定義域或處理序層級的隔離,而不需要您自行管理應用程式定義域。This model provides isolation at the application domain or process level, without requiring you to manage application domains yourself. 如需增益集模型的資訊,請參閱增益集和擴充性For information about the add-in model, see Add-ins and Extensibility.

考慮使用全域組件快取Consider Using the Global Assembly Cache

將組件放入全域組件快取以受益於應用程式基底外部的共用組件路徑,而不會遺失預設載入內容的優點或造成其他內容的缺點。Place assemblies in the global assembly cache to get the benefit of a shared assembly path that is outside the application base, without losing the advantages of the default load context or taking on the disadvantages of the other contexts.

考慮使用應用程式定義域Consider Using Application Domains

如果您判斷無法在應用程式的探查路徑中部署部分組件,請考慮針對這些組件建立新的應用程式定義域。If you determine that some of your assemblies cannot be deployed in the application's probing path, consider creating a new application domain for those assemblies. 使用 AppDomainSetup 建立新的應用程式定義域,並使用 AppDomainSetup.ApplicationBase 屬性指定包含您想要載入之組件的路徑。Use an AppDomainSetup to create the new application domain, and use the AppDomainSetup.ApplicationBase property to specify the path that contains the assemblies you want to load. 如果您有多個要探查的目錄,則可以將 ApplicationBase 設定為根目錄,並使用 AppDomainSetup.PrivateBinPath 屬性來識別要探查的子目錄。If you have multiple directories to probe, you can set the ApplicationBase to a root directory and use the AppDomainSetup.PrivateBinPath property to identify the subdirectories to probe. 或者,您可以建立多個應用程式定義域,並將每個應用程式定義域的 ApplicationBase 設定為其組件的適當路徑。Alternatively, you can create multiple application domains and set the ApplicationBase of each application domain to the appropriate path for its assemblies.

請注意,您可以使用 Assembly.LoadFrom 方法來載入這些組件。Note that you can use the Assembly.LoadFrom method to load these assemblies. 因為它們現在是在探查路徑中,所以會將其載入到預設載入內容,而非載入來源內容。Because they are now in the probing path, they will be loaded into the default load context instead of the load-from context. 不過,建議您切換成 Assembly.Load 方法,並提供完整組件顯示名稱,確保一律使用正確版本。However, we recommend that you switch to the Assembly.Load method and supply full assembly display names to ensure that correct versions are always used.

另請參閱See also