System.Runtime.Loader.AssemblyLoadContext class

This article provides supplementary remarks to the reference documentation for this API.

The AssemblyLoadContext represents a load context. Conceptually, a load context creates a scope for loading, resolving, and potentially unloading a set of assemblies.

The AssemblyLoadContext exists primarily to provide assembly loading isolation. It allows multiple versions of the same assembly to be loaded within a single process. It replaces the isolation mechanisms provided by multiple AppDomain instances in .NET Framework.

Note

Usage in the runtime

The runtime implements two assembly load contexts:

Application usage

An application can create its own AssemblyLoadContext to create a custom solution for advanced scenarios. The customization focuses on defining dependency resolution mechanisms.

The AssemblyLoadContext provides two extension points to implement managed assembly resolution:

  1. The AssemblyLoadContext.Load(AssemblyName) method provides the first chance for the AssemblyLoadContext to resolve, load, and return the assembly. If the AssemblyLoadContext.Load(AssemblyName) method returns null, the loader tries to load the assembly into the AssemblyLoadContext.Default.
  2. If the AssemblyLoadContext.Default is unable to resolve the assembly, the original AssemblyLoadContext gets a second chance to resolve the assembly. The runtime raises the Resolving event.

Additionally, the AssemblyLoadContext.LoadUnmanagedDll(String) virtual method allows customization of the default unmanaged assembly resolution. The default implementation returns null, which causes the runtime search to use its default search policy. The default search policy is sufficient for most scenarios.

Technical challenges

  • It's not possible to load multiple versions of the runtime in a single process.

    Caution

    Loading multiple copies or different versions of framework assemblies can lead to unexpected and hard-to-diagnose behavior.

    Tip

    Use process boundaries with remoting or interprocess communication to solve this isolation problem.

  • The timing of assembly loading can make testing and debugging difficult. Assemblies are typically loaded without their dependencies immediately being resolved. The dependencies are loaded as they are needed:

    • When code branches into a dependent assembly.
    • When code loads resources.
    • When code explicitly loads assemblies.
  • The implementation of AssemblyLoadContext.Load(AssemblyName) can add new dependencies that may need to be isolated to allow different versions to exist. The most natural implementation would place these dependencies in the default context. Careful design can isolate the new dependencies.

  • The same assembly is loaded multiple times into different contexts.

    • This can lead to confusing error messages, for example "Unable to cast object of type 'Sample.Plugin' to type 'Sample.Plugin'".
    • Marshaling across isolation boundaries is non-trivial. A typical solution is to use an interface defined in an assembly that's only loaded into the default load context.