You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

.NET与.NET Framework跨平台运行时程序集加载的问题与适配咨询

.NET与.NET Framework跨平台运行时程序集加载的问题与适配咨询

我来帮你拆解这两个问题,结合你的项目场景一步步说清楚:

问题1:为什么先调用Type.GetType("DataClasses.TestDataClass")会导致后续无法解析类型?

这个是CLR的类型解析缓存机制在“搞鬼”。当你第一次调用不带程序集限定名的Type.GetType("DataClasses.TestDataClass")时,CLR会在当前已经加载的所有程序集里查找这个类型,找不到之后,会把这次类型查询的失败结果缓存起来

等你后来用AssemblyLoadContext加载了DataClasses.dll,再用完整限定名Type.GetType("DataClasses.TestDataClass, DataClasses")查询时,CLR会先检查之前的失败缓存,直接返回null,根本不会去新加载的程序集里重新查找——它默认你之前查不到的类型,现在也查不到。

解决思路:

要么:

  • 永远不要在加载目标程序集之前发起任何类型查询(不管带不带程序集限定名);
  • 或者在加载程序集之后,直接通过加载后的Assembly实例获取类型,绕开Type.GetType的缓存:比如assembly.GetType("DataClasses.TestDataClass"),这样就不受之前的失败缓存影响。

问题2:在netstandard2.0类库中实现程序集的一次性加载(兼容.NET/.NET Framework)

你用AppDomain.CurrentDomain.AssemblyResolve的思路是完全正确的,问题出在没有缓存已经加载的程序集,导致每次触发事件都会重新加载DLL。我们只要加个线程安全的静态缓存,就能实现只加载一次的需求,同时完美兼容.NET和.NET Framework:

改进后的实现代码:

using System;
using System.IO;
using System.Reflection;
using System.Collections.Concurrent;

namespace AssemblyResolveTestClassLibrary
{
    public static class AssemblyResolver
    {
        // 用线程安全字典缓存已加载的程序集,避免重复加载
        private static readonly ConcurrentDictionary<string, Assembly> _loadedAssemblies = new ConcurrentDictionary<string, Assembly>();
        private const string TargetAssemblyName = "DataClasses";

        static AssemblyResolver()
        {
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        }

        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            var requestedAssemblyName = new AssemblyName(args.Name).Name;

            // 先检查缓存:如果已经加载过目标程序集,直接返回缓存实例
            if (_loadedAssemblies.TryGetValue(TargetAssemblyName, out var cachedAssembly))
            {
                return cachedAssembly;
            }

            // 只处理目标程序集的解析请求
            if (requestedAssemblyName == TargetAssemblyName)
            {
                var assemblyPath = Path.Combine(Directory.GetCurrentDirectory(), $"{TargetAssemblyName}.dll");
                if (File.Exists(assemblyPath))
                {
                    // 加载程序集并写入缓存
                    var loadedAssembly = Assembly.LoadFile(assemblyPath);
                    _loadedAssemblies.TryAdd(TargetAssemblyName, loadedAssembly);
                    return loadedAssembly;
                }
            }

            return null;
        }

        public static Type GetTestDataClassType()
        {
            // 优先从缓存的程序集直接取类型,绕开Type.GetType的潜在缓存问题
            if (_loadedAssemblies.TryGetValue(TargetAssemblyName, out var assembly))
            {
                return assembly.GetType("DataClasses.TestDataClass");
            }
            // 缓存未命中时,用限定名触发AssemblyResolve事件加载
            return Type.GetType($"DataClasses.TestDataClass, {TargetAssemblyName}");
        }
    }
}

关键改进点说明:

  1. 线程安全缓存:用ConcurrentDictionary保存已加载的程序集,确保多线程场景下也只会加载一次;
  2. 绕开类型查询缓存:优先通过缓存的Assembly实例获取类型,比Type.GetType更可靠,不会受之前的失败查询缓存影响;
  3. 跨平台兼容AppDomain.CurrentDomain.AssemblyResolve在netstandard2.0、.NET Framework和.NET(Core 3.0+)下都能正常工作,完全适配你的IPC类库场景。

结合你的IPC场景补充:

当Newtonsoft.Json尝试反序列化SampleResult类型时,CLR会自动触发AssemblyResolve事件,此时我们的缓存机制会自动加载一次DataClasses程序集,后续所有反序列化请求都会复用已加载的程序集实例,不会重复加载。

另外注意:如果后续要支持多个版本的DataClasses,可以把缓存的key改成完整的程序集名称(而不只是简单名称),这样能区分不同版本的程序集。

内容来源于stack exchange

火山引擎 最新活动