docs/design/features/assemblyloadcontext.md
LoadContext can be viewed as a container for assemblies, their code and data (e.g. statics). Whenever an assembly is loaded, it is loaded within a load context - independent of whether the load was triggered explicitly (e.g. via Assembly.Load), implicitly (e.g. resolving static assembly references from the manifest) or dynamically (by emitting code on the fly).
This concept is not new to .NET Core but has existed since the days of .NET Framework (see this for details) where it operated behind the scenes and not exposed for the developer to interact with, aside from loading your assembly in one based upon the API used to perform the load.
In .NET Core, we have exposed a managed API surface that developers can use to interact with it - to inspect loaded assemblies or create their own LoadContext instance. Here are some of the scenarios that motivated this work:
Every .NET app has a LoadContext instance created during .NET Runtime startup that we will refer to as the Default LoadContext. All application assemblies (including their transitive closure) are loaded within this LoadContext instance.
For scenarios that wish to have isolation between loaded assemblies, applications can create their own LoadContext instance by deriving from System.Runtime.Loader.AssemblyLoadContext type and loading the assemblies within that instance.
Multiple assemblies with the same simple name cannot be loaded into a single load context (Default or Custom). Also, .NET Core ignores strong name token for assembly binding process.
If an assembly A1 triggers the load of an assembly C1, the latter's load is attempted within the LoadContext instance of the former (which is also known as the RequestingAssembly or ParentAssembly).
Dynamically generated assemblies add a slight twist since they do not have a ParentAssembly/RequestingAssembly per-se. Thus, they are associated with the load context of their Creator Assembly and any subsequent loads (static or dynamic) will use that load context.
If the assembly was already present in A1's context, either because we had successfully loaded it earlier, or because we failed to load it for some reason, we return the corresponding status (and assembly reference for the success case).
However, if C1 was not found in A1's context, the Load method override in A1's context is invoked.
For Custom LoadContext, this override is an opportunity to load an assembly before the fallback (see below) to Default LoadContext is attempted to resolve the load.
For Default LoadContext, this override always returns null since Default Context cannot override itself.
If the Load method override does not resolve the load, fallback to Default LoadContext is attempted to resolve the load incase the assembly was already loaded there. If the operating context is Default LoadContext, there is no fallback attempted since it has nothing to fallback to.
If the Default LoadContext fallback also did not resolve the load (or was not applicable), the Resolving event is invoked against A1's load context. This is the last opportunity to attempt to resolve the assembly load. If there are no subscribers for this event, or neither resolved the load, a FileNotFoundException is thrown.
Custom LoadContext can override the AssemblyLoadContext.LoadUnmanagedDll method to intercept PInvokes from within the LoadContext instance so that can be resolved from custom binaries. If not overridden, or if the resolution is not able to resolve the PInvoke, the default PInvoke mechanism will be used as fallback.
Tests are present here.
Most of the AssemblyLoadContext API surface is self-explanatory. Key APIs/Properties, though, are described below:
This property will return a reference to the Default LoadContext.
This method should be overridden in a Custom LoadContext if the intent is to override the assembly resolution that would be done during fallback to Default LoadContext
This method can be used to load an assembly into a load context different from the load context of the currently executing assembly. The assembly will be loaded into the load context on which the method is called. If the context can't resolve the assembly in its Load method the assembly loading will defer to the Default load context. In such case it's possible the loaded assembly is from the Default context even though the method was called on a non-default context.
Calling this method directly on the AssemblyLoadContext.Default will only load the assembly from the Default context. Depending on the caller the Default may or may not be different from the load context of the currently executing assembly.
This method does not "forcefully" load the assembly into the specified context. It basically initiates a bind to the specified assembly name on the specified context. That bind operation will go through the full binding resolution logic which is free to resolve the assembly from any context (in reality the most likely outcome is either the specified context or the default context). This process is described above.
To make sure a specified assembly is loaded into the specified load context call AssemblyLoadContext.LoadFromAssemblyPath and specify the path to the assembly file.
This event is raised to give the last opportunity to a LoadContext instance to attempt to resolve a requested assembly that has neither been resolved by Load method, nor by fallback to Default LoadContext.
As part of .NET Standard 2.0 effort, certain assembly load APIs off the Assembly type, which were present in Desktop .NET Framework, have been brought back. The following maps the APIs to the load context in which they will load the assembly:
If you need to influence the load process or the load context in which assemblies are loaded, please look at the various Load* APIs exposed by AssemblyLoadContext API surface.