File: Hosting\AssemblyLoader\CoreAssemblyLoaderImpl.cs
Web Access
Project: src\src\Scripting\Core\Microsoft.CodeAnalysis.Scripting.csproj (Microsoft.CodeAnalysis.Scripting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#if NET
 
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
 
namespace Microsoft.CodeAnalysis.Scripting.Hosting
{
    internal sealed class CoreAssemblyLoaderImpl : AssemblyLoaderImpl
    {
        private readonly LoadContext _inMemoryAssemblyContext;
 
        internal CoreAssemblyLoaderImpl(InteractiveAssemblyLoader loader)
            : base(loader)
        {
            _inMemoryAssemblyContext = new LoadContext(Loader, null);
        }
 
        public override Assembly LoadFromStream(Stream peStream, Stream pdbStream)
        {
            return _inMemoryAssemblyContext.LoadFromStream(peStream, pdbStream);
        }
 
        public override AssemblyAndLocation LoadFromPath(string path)
        {
            // Create a new context that knows the directory where the assembly was loaded from
            // and uses it to resolve dependencies of the assembly. We could create one context per directory,
            // but there is no need to reuse contexts.
            var assembly = new LoadContext(Loader, Path.GetDirectoryName(path)).LoadFromAssemblyPath(path);
 
            return new AssemblyAndLocation(assembly, path, GlobalAssemblyCache: false);
        }
 
        public override void Dispose()
        {
            // nop
        }
 
        private sealed class LoadContext : AssemblyLoadContext
        {
            private readonly string? _loadDirectory;
            private readonly InteractiveAssemblyLoader _loader;
 
            internal LoadContext(InteractiveAssemblyLoader loader, string? loadDirectory)
            {
                _loader = loader;
                _loadDirectory = loadDirectory;
 
                // CoreCLR resolves assemblies in steps:
                //
                //   1) Call AssemblyLoadContext.Load -- our context returns null
                //   2) TPA list
                //   3) Default.Resolving event
                //   4) AssemblyLoadContext.Resolving event -- hooked below
                // 
                // What we want is to let the default context load assemblies it knows about (this includes already loaded assemblies,
                // assemblies in AppPath, platform assemblies, assemblies explciitly resolved by the App by hooking Default.Resolving, etc.).
                // Only if the assembly can't be resolved that way, the interactive resolver steps in.
                //
                // This order is necessary to avoid loading assemblies twice (by the host App and by interactive loader).
 
                Resolving += (_, assemblyName) =>
                    _loader.ResolveAssembly(AssemblyIdentity.FromAssemblyReference(assemblyName), _loadDirectory);
            }
 
            protected override Assembly? Load(AssemblyName assemblyName) => null;
        }
    }
}
#endif