Created
February 4, 2026 22:07
-
-
Save ricaun/76ee1bd6c9a5c26bfa77bd191adbcafc to your computer and use it in GitHub Desktop.
Custom AssemblyLoadContext with multiple AssemblyDependencyResolver.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Reflection; | |
| using System.Runtime.Loader; | |
| /// <summary> | |
| /// A collectible <see cref="AssemblyLoadContext"/> that aggregates multiple | |
| /// <see cref="AssemblyDependencyResolver"/> instances to resolve managed and unmanaged dependencies. | |
| /// Useful for loading component assemblies and their native dependencies from specific paths | |
| /// while keeping them isolated and collectible. | |
| /// </summary> | |
| public class CustomAssemblyLoadContext : AssemblyLoadContext | |
| { | |
| /// <summary> | |
| /// Collection of <see cref="AssemblyDependencyResolver"/> instances used to resolve | |
| /// assembly and native library paths for this load context. | |
| /// </summary> | |
| private readonly List<AssemblyDependencyResolver> _resolvers = new List<AssemblyDependencyResolver>(); | |
| /// <summary> | |
| /// Tracks the root component assembly paths that have been registered in <see cref="_resolvers"/>. | |
| /// This prevents adding duplicate resolvers for the same path. | |
| /// </summary> | |
| private readonly List<string> _resolverPaths = new List<string>(); | |
| /// <summary> | |
| /// Initializes a new instance of the <see cref="CustomAssemblyLoadContext"/> class with the specified name | |
| /// and registers an initial resolver based on <paramref name="assemblyPath"/>. | |
| /// </summary> | |
| /// <param name="contextName">The name for the load context. Used for diagnostics and identification.</param> | |
| /// <param name="assemblyPath">The path to a component assembly (or its folder) used to initialize a dependency resolver.</param> | |
| public CustomAssemblyLoadContext(string contextName, string assemblyPath) : base(contextName, isCollectible: true) | |
| { | |
| AddResolver(assemblyPath); | |
| } | |
| /// <summary> | |
| /// Adds an <see cref="AssemblyDependencyResolver"/> for the provided component assembly path. | |
| /// If the path is null, empty, whitespace, or already added this method will either throw | |
| /// or return without adding a duplicate. | |
| /// </summary> | |
| /// <param name="componentAssemblyPath">The path to a component assembly file or folder used to create a resolver.</param> | |
| /// <exception cref="ArgumentException">Thrown when <paramref name="componentAssemblyPath"/> is null, empty, or whitespace.</exception> | |
| public void AddResolver(string componentAssemblyPath) | |
| { | |
| if (string.IsNullOrWhiteSpace(componentAssemblyPath)) | |
| throw new ArgumentException(nameof(componentAssemblyPath)); | |
| if (_resolverPaths.Contains(componentAssemblyPath)) return; | |
| _resolvers.Add(new AssemblyDependencyResolver(componentAssemblyPath)); | |
| _resolverPaths.Add(componentAssemblyPath); | |
| } | |
| /// <summary> | |
| /// Overrides the managed assembly resolution logic for this load context. | |
| /// Iterates the registered <see cref="AssemblyDependencyResolver"/> instances and attempts | |
| /// to resolve the requested <paramref name="assemblyName"/> to a file path. When found, | |
| /// the assembly is loaded from that path into this context. | |
| /// </summary> | |
| /// <param name="assemblyName">The <see cref="AssemblyName"/> of the requested assembly.</param> | |
| /// <returns> | |
| /// The loaded <see cref="Assembly"/> if resolution succeeded; otherwise <c>null</c> | |
| /// to allow the default load behavior or other contexts to attempt resolution. | |
| /// </returns> | |
| protected override Assembly Load(AssemblyName assemblyName) | |
| { | |
| foreach (var resolver in _resolvers) | |
| { | |
| var path = resolver.ResolveAssemblyToPath(assemblyName); | |
| if (path != null) | |
| { | |
| return LoadFromAssemblyPath(path); | |
| } | |
| } | |
| return null; | |
| } | |
| /// <summary> | |
| /// Overrides the unmanaged DLL resolution logic for this load context. | |
| /// Iterates the registered <see cref="AssemblyDependencyResolver"/> instances and attempts | |
| /// to resolve the requested unmanaged library name to a file path. When found, the native | |
| /// library is loaded from that path into this context. | |
| /// </summary> | |
| /// <param name="unmanagedDllName">The name of the unmanaged library being requested.</param> | |
| /// <returns> | |
| /// A platform-specific pointer to the loaded native library if resolution succeeded; | |
| /// otherwise <see cref="IntPtr.Zero"/>. | |
| /// </returns> | |
| protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) | |
| { | |
| foreach (var resolver in _resolvers) | |
| { | |
| var path = resolver.ResolveUnmanagedDllToPath(unmanagedDllName); | |
| if (path != null) | |
| { | |
| return LoadUnmanagedDllFromPath(path); | |
| } | |
| } | |
| return IntPtr.Zero; | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is used in the Isolator.Fody