| File: System\Reflection\PathAssemblyResolver.cs | Web Access |
| Project: src\runtime\src\libraries\System.Reflection.MetadataLoadContext\src\System.Reflection.MetadataLoadContext.csproj (System.Reflection.MetadataLoadContext) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.IO; namespace System.Reflection { /// <summary> /// An assembly resolver that uses paths to every assembly that may be loaded. /// The file name is expected to be the same as the assembly's simple name. /// Multiple assemblies can exist on disk with the same name but in different directories. /// A single instance of PathAssemblyResolver can be used with multiple MetadataAssemblyResolver instances. /// </summary> /// <remarks> /// In order for an AssemblyName to match to a loaded assembly, AssemblyName.Name must be equal (casing ignored). /// - If AssemblyName.PublicKeyToken is specified, it must be equal. /// - If AssemblyName.PublicKeyToken is not specified, assemblies with no PublicKeyToken are selected over those with a PublicKeyToken. /// - If more than one assembly matches, the assembly with the highest Version is returned. /// - CultureName is ignored. /// </remarks> public class PathAssemblyResolver : MetadataAssemblyResolver { private readonly Dictionary<string, List<string>> _fileToPaths = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); /// <summary> /// Initializes a new instance of the <see cref="System.Reflection.PathAssemblyResolver"/> class. /// </summary> /// <exception cref="System.ArgumentNullException">Thrown when assemblyPaths is null.</exception> /// <exception cref="System.ArgumentException">Thrown when a path is invalid.</exception> public PathAssemblyResolver(IEnumerable<string> assemblyPaths) { ArgumentNullException.ThrowIfNull(assemblyPaths); foreach (string path in assemblyPaths) { if (string.IsNullOrEmpty(path)) throw new ArgumentException(SR.Format(SR.Arg_InvalidPath, path), nameof(assemblyPaths)); string file = Path.GetFileNameWithoutExtension(path); if (file.Length == 0) throw new ArgumentException(SR.Format(SR.Arg_InvalidPath, path), nameof(assemblyPaths)); List<string>? paths; if (!_fileToPaths.TryGetValue(file, out paths)) { _fileToPaths.Add(file, paths = new List<string>()); } paths.Add(path); } } public override Assembly? Resolve(MetadataLoadContext context, AssemblyName assemblyName) { Debug.Assert(assemblyName.Name != null); Assembly? candidateWithSamePkt = null; Assembly? candidateIgnoringPkt = null; if (_fileToPaths.TryGetValue(assemblyName.Name, out List<string>? paths)) { ReadOnlySpan<byte> pktFromName = assemblyName.GetPublicKeyToken(); foreach (string path in paths) { Assembly assemblyFromPath = context.LoadFromAssemblyPath(path); AssemblyName assemblyNameFromPath = assemblyFromPath.GetName(); if (assemblyName.Name.Equals(assemblyNameFromPath.Name, StringComparison.OrdinalIgnoreCase)) { ReadOnlySpan<byte> pktFromAssembly = assemblyNameFromPath.GetPublicKeyToken(); // Find exact match on PublicKeyToken including treating no PublicKeyToken as its own entry. if (pktFromName.SequenceEqual(pktFromAssembly)) { // Pick the highest version. if (candidateWithSamePkt == null || assemblyNameFromPath.Version > candidateWithSamePkt.GetName().Version) { candidateWithSamePkt = assemblyFromPath; } } // If assemblyName does not specify a PublicKeyToken, or assemblyName is marked 'Retargetable', // then still consider those with a PublicKeyToken and take the highest version available. else if ((candidateWithSamePkt == null && pktFromName.IsEmpty) || ((assemblyName.Flags & AssemblyNameFlags.Retargetable) != 0)) { // Pick the highest version. if (candidateIgnoringPkt == null || assemblyNameFromPath.Version > candidateIgnoringPkt.GetName().Version) { candidateIgnoringPkt = assemblyFromPath; } } } } } return candidateWithSamePkt ?? candidateIgnoringPkt; } } }