由于缺少程序集(.NET 核心),通过 MetadataLoadContext 检查类型失败

Inspect Types via MetadataLoadContext fails due to missing assembly (.NET core)

我想检查程序集是否具有特定类型,而无需在当前范围内加载程序集,这可通过 .NET Core 3 中的 MetadataLoadContext 获得。

但是如果我尝试下面的例子

internal static class Program
{
    // ReSharper disable once UnusedParameter.Local
    private static void Main(string[] args)
    {
        var paths = new string[] { @"Plugin.dll" };
        var resolver = new PathAssemblyResolver(paths);
        var pluginInterface = typeof(IPlugin);
        using (var context = new MetadataLoadContext(resolver))
        {
            var assembly =
                context.LoadFromAssemblyName(@"Plugin");
            foreach (var type in assembly.GetTypes())
            {
                if (type.IsClass && pluginInterface.IsAssignableFrom(type))
                    Console.WriteLine("found");
            }
        }
    }
}

我遇到异常

System.IO.FileNotFoundException: Could not find core assembly. Either specify a valid core assembly name in the MetadataLoadContext constructor or provide a MetadataAssemblyResolver that can load the core assembly.

var context = new MetadataLoadContext(resolver)

核心组件是什么意思?或者我做错了什么? https://blog.vincentbitter.nl/net-core-3-0/ 似乎对我不起作用。

提供 .NET 核心库的以下路径有效

  var paths = new string[] {@"Plugin.dll", @"netstandard.dll", "System.Runtime.dll"};

现有答案对我不起作用(.NET Core 2.1)。它失败并出现 System.Runtime 未找到的错误。如果我硬编码 System.Runtime 的完整路径,它对其他程序集失败,例如 System.Private.CoreLib。当一种类型不是来自 MetadataLoadContext 时,通过 IsAssignableFrom 检查类型似乎也不起作用。

程序集加载错误的可能解决方案是包括所有 BCL 程序集(RuntimeEnvironment.GetRuntimeDirectory 返回的目录中的所有 .dll 文件)。这感觉有些愚蠢,因为并非所有这些实际上都是托管程序集,但它似乎有效。下面是搜索通过 MetadataLoadContext 实现接口的类型的完整示例:

using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;

namespace MetadataLoadContextSample
{
    class Program
    {
        static int Main(string[] args)
        {
            string inputFile = @"Plugin.dll";

            string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");                        
            var paths = new List<string>(runtimeAssemblies);
            paths.Add(inputFile);            
            var resolver = new PathAssemblyResolver(paths);
            var context = new MetadataLoadContext(resolver);

            using (context)
            {                
                Assembly assembly = context.LoadFromAssemblyPath(inputFile);
                AssemblyName name = assembly.GetName();

                foreach (TypeInfo t in assembly.GetTypes())
                {
                    try
                    {
                        if (t.IsClass && t.GetInterface("IPlugin") != null)
                        {
                            Console.WriteLine(t.Name);
                        }
                    }
                    catch (FileNotFoundException ex)
                    {                        
                        Console.WriteLine("FileNotFoundException: " + ex.Message);
                    }
                    catch (TypeLoadException ex)
                    {
                        Console.WriteLine("TypeLoadException: " + ex.Message);
                    }
                }
            }

            Console.ReadLine();
            return 0;
        }
    }
}

这是一种适用于 net6.0 的方法。该方法从作为参数提供的程序集中提取自定义属性。

public static FreshInfo GetInfo(string pathToMainExecutable) {
        if (string.IsNullOrWhiteSpace(pathToMainExecutable))
            throw new ArgumentException(@"Value cannot be null or whitespace.", nameof(pathToMainExecutable));
    
        if (File.Exists(pathToMainExecutable) == false)
            throw new FileNotFoundException($"File [{pathToMainExecutable}] does not exists on disk.");
    
        var runtimeDirectory = RuntimeEnvironment.GetRuntimeDirectory();
    
        var pathToSystemRuntime = Path.Combine(runtimeDirectory, "System.Runtime.dll");
        if (File.Exists(pathToSystemRuntime) == false)
            throw new FileNotFoundException($"Could not find [{pathToSystemRuntime}].");
    
        var pathToSystemPrivateCoreLib = Path.Combine(runtimeDirectory, "System.Private.CoreLib.dll");
        if (File.Exists(pathToSystemPrivateCoreLib) == false)
            throw new FileNotFoundException($"Could not find [{pathToSystemPrivateCoreLib}].");
    
        // make sure that we are referring to the .net dll/assembly but not the exe bootstrapper
        // exe file in net6.0/core is not a managed file it's a native executable
        // TODO do not assume that the exe file has the same name as the dll file
        // managed dll filename can be extracted from a VERSIONINFO Resource in the native exe 
        pathToMainExecutable = pathToMainExecutable.ReplaceEnd("exe", "dll");
    
        var assemblyNames = new List<string> {
            Path.GetFileName(pathToMainExecutable)
          , "Fresh.Updater.dll"
          , pathToSystemRuntime
          , pathToSystemPrivateCoreLib
        };
    
        var metadataAssemblyResolver = new PathAssemblyResolver(assemblyNames);
    
        using (var mlc = new MetadataLoadContext(metadataAssemblyResolver)) {
            var mainAssembly = mlc.LoadFromAssemblyPath(pathToMainExecutable);
    
            var buildTime = ExtractBuildTime(mainAssembly);
            var appId     = ExtractAppId(mainAssembly);
    
            var appFolder = Path.GetDirectoryName(pathToMainExecutable);
            return new FreshInfo(appId, buildTime) {
                AppFolder = appFolder
            };
        }
    }