.Net Core 中仅反射程序集加载

And*_*ens 5 c# reflection .net-assembly .net-core

我有一个 .Net Framework WPF 应用程序,目前正在迁移到 .Net6。启动时,它会检查可执行文件夹中的某些程序集,查找具有自定义程序集属性的程序集。然后,那些具有此功能的应用程序将被加载到当前的应用程序域中。(请注意,其中一些程序集可能已经位于应用程序域中,因为它们是正在运行的应用程序的解决方案中的项目)。

这是 4.x 代码:

private void LoadAssemblies(string folder)
{
    AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
        (s, e) => Assembly.ReflectionOnlyLoad(e.Name);

    var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
    foreach (var assemblyFile in assemblyFiles)
    {
        var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile);
        if (ContainsCustomAttr(reflectionOnlyAssembly))
        {
            var assembly = Assembly.LoadFrom(assemblyFile);
            ProcessAssembly(assembly);
        }
    }
}
  
Run Code Online (Sandbox Code Playgroud)

自定义程序集属性(此代码正在查找)具有一个字符串属性,其中包含该程序集中 XAML 资源文件的路径。该ProcessAssembly()方法将此资源文件添加到应用程序的合并字典中,如下所示:

var resourceUri = string.Format(
    "pack://application:,,,/{0};component/{1}",
    assembly.GetName().Name,
    mimicAssemblyAttribute.DataTemplatePath);

var uri = new Uri(resourceUri, UriKind.RelativeOrAbsolute);
application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = uri });
Run Code Online (Sandbox Code Playgroud)

重申一下,所有这些都在 .Net 4.x 应用程序中正常工作。

另一方面,.Net6不支持仅反射加载,也不能创建第二个应用程序域来加载程序集。我重写了上面的代码,将正在检查的程序集加载到我所理解的临时的、可卸载的上下文中:

    private void LoadAssemblies(string folder)
    {
        var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
        using (var ctx = new TempAssemblyLoadContext(AppDomain.CurrentDomain.BaseDirectory))
        {
            foreach (var assemblyFile in assemblyFiles)
            {
                var assm = ctx.LoadFromAssemblyPath(assemblyFile);
                if (ContainsCustomAttr(assm))
                {
                    var assm2 = Assembly.LoadFrom(assemblyFile);
                    ProcessAssembly(assm2);
                }
            }
        }
    }

    private class TempAssemblyLoadContext : AssemblyLoadContext, IDisposable
    {
        private AssemblyDependencyResolver _resolver;

        public TempAssemblyLoadContext(string readerLocation)
            : base(isCollectible: true)
        {
            _resolver = new AssemblyDependencyResolver(readerLocation);
        }

        public void Dispose()
        {
            Unload();
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            var path = _resolver.ResolveAssemblyToPath(assemblyName);
            if (path != null)
            {
                return LoadFromAssemblyPath(path);
            }

            return null;
        }

        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (path != null)
            {
                return LoadUnmanagedDllFromPath(path);
            }

            return IntPtr.Zero;
        }
    }
Run Code Online (Sandbox Code Playgroud)

(请注意 ProcessAssembly() 方法未更改)。

这段代码“有效”,因为它只是走过场而不会崩溃。然而,稍后当应用程序开始创建视图时,我收到以下异常:

组件 ' . .ModeSelectorView' 没有由 URI '/ 标识的资源;组件/视图/模式选择器/modeselectorview.xaml'。

该特定视图驻留在该应用程序解决方案的项目中,因此该程序集已位于应用程序域中。该程序集还包含该自定义属性,因此上面的代码将尝试加载它,尽管我认为Assembly.LoadFrom()不应再次加载相同的程序集?

为了以防万一,我修改了 LoadAssemblies() 方法中的“if”块以忽略应用程序域中已有的程序集:

if (ContainsCustomAttr(assm) && !AppDomain.CurrentDomain.GetAssemblies().Contains(assm))
Run Code Online (Sandbox Code Playgroud)

果然,断点显示相关程序集(包含该视图)被忽略并且加载到应用程序域中。然而,我仍然遇到同样的异常。事实上,我可以注释掉整个“if”块,这样就不会将程序集加载到应用程序域中,并且我仍然收到异常,表明这是由于将程序集加载到 AssemblyLoadContext 中引起的。此外,断点显示在退出 LoadAssemblies() 方法中的“using”块后,上下文正在通过其 Dispose() 方法卸载。

编辑:即使注释掉“if”块,该方法末尾的断点也显示由 ctx.LoadFromAssemblyPath() 加载的所有程序集最终都在 AppDomain.Current 中。我不明白什么?上下文是应用程序域的一部分而不是单独的“区域”吗?如何以与我在 .Net 4.x 中使用的“仅反射”方法类似的方式实现程序集的“隔离”加载?

And*_*ens 7

好的,我找到了答案,那就是使用MetadataLoadContext。这本质上是仅反射加载的 .Net Core 替代品:

private void LoadAssemblies(string folder)
{
    // The load context needs access to the .Net "core" assemblies...
    var allAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.Client.dll").ToList();
    // .. and the assemblies that I need to examine.
    var assembliesToExamine = Directory.GetFiles(folder, "NuIns.CoDaq.*.Client.dll");
    allAssemblies.AddRange(assembliesToExamine);

    var resolver = new PathAssemblyResolver(allAssemblies);
    using (var mlc = new MetadataLoadContext(resolver))
    {
        foreach (var assemblyFile in assembliesToExamine)
        {
            var assm = mlc.LoadFromAssemblyPath(assemblyFile);
            if (ContainsCustomAttr(assm))
            {
                var assm2 = Assembly.LoadFrom(assemblyFile);
                AddMimicAssemblyInfo(assm2);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)