c# - 用 AssemblyLoadContext 替换 .NET AppDomain

标签 c# .net .net-core .net-6.0

我正在尝试将 .NET Framework 类库升级到 .NET 6,但在 AppDomains 方面遇到了一些问题。我最初并没有编写该库,但我的理解是它创建了一个与 .NET 运行时为其创建的 AppDomain 分开的 AppDomain,通过调用 AppDomain.CreateInstanceAndUnwrap< 使用这个新的应用程序域实例化它自己的一些类型 然后这些实例加载其他第三方程序集以供检查。我推测它以这种方式实例化自己的类型的原因是为了将处理第三方程序集的代码和这些程序集本身与库的执行上下文完全隔离。

阅读文档和 .NET 博客,我了解到 AppDomains 在 .NET Core 或更高版本中已停用,以隔离方式加载程序集的正确方法是使用 AssemblyLoadContext。因此,我用自己的子类覆盖了 AssemblyLoadContext,并使用它来加载库的程序集,使用 Assembly.CreateInstance 实例化其类型,并使用这些实例加载第三方程序集,就像我所做的那样在线阅读建议这是确保您将程序集加载到与您的代码自身分开的上下文中的最干净的方法。

这种方法存在一些不确定性——我不确定是否仍然需要在单独的上下文中实例化我们库的类型。如果是,我在将 Assembly.CreateInstance 返回的 System.Object 中的实例转换为正确的原始类型时遇到问题。

这是我的 AssembyLoadContext 导数:

namespace MyNamespace
{
    class MyLoadContext : AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;
        public MyLoadContext(string basePath)
        {
            _resolver = new AssemblyDependencyResolver(basePath);

           
        }

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

            return null;

        }
    }

}

这是我的类,它尝试从与该类相同的程序集中加载其他类型:

namespace MyNamespace
{

    public class MyClass
    {

        MyClass1 _ClassToLoad1
        MyClass2 _ClassToLoad2;
        
        MyLoadContext _assemblyLoadContext;
                        

        
        public MyClass()
        {
        
        }
        
        public LoadClasses
        {
            _assemblyLoadContext = new MyLoadContext((Assembly.GetExecutingAssembly().Location));
            Assembly assembly = _assemblyLoadContext.LoadFromAssemblyName(new AssemblyName("MyAssembly"));
            
            
            dynamic myClassObj1 = assembly.CreateInstance("MyNamespace.MyClass1");
            dynamic myClassObj2 = assembly.CreateInstance("MyNamespace.MyClass2");
            
            _classToLoad1 = (MyClass1) myClassObj1;
            _classToLoad2 = (MyClass2) myClassObj2;

            
        }
        

    }


}

当我尝试将 myClassObj1myClassObj2 转换为 MyClass1MyClass2 的实例时,它会抛出 InvalidCastException因为这些对象与执行转换的代码位于不同的 AssemblyLoadContext 中。

我读到 .NET 运行时将在不同 AssemblyLoadContext 中创建的类型视为不平等,因此转换失败并将 null 分配给具有正确类型的变量,即使它们来自同一程序集并且具有相同的名称和实现.我不确定如何解决这个问题,尽管我将 Assembly.Create 实例的结果分配给一个 dynamic 并调用它,这看起来像是 hack。

我读到一种方法是将类型外化到它们自己的程序集中,但这似乎有点矫枉过正,并且意味着管理另一个库的额外开销。我也不知道为什么在使用概念上实现相同功能的单独 AppDomain 时这不是问题。

最佳答案

我相信你的问题可以追溯到

Types are per-assembly; if you have "the same" assembly loaded twice, then types in each "copy" of the assembly are not considered to be the same type.

https://stackoverflow.com/a/2500820/1462295

这很难说,但我认为从您的代码中您尝试加载的程序集已经加载。否则,您尝试转换为的类型将不存在。 (旁注,由于类型已知,您可以跳过 dynamic ,它看起来只是用来避免强制转换异常)。如果您有对另一个程序集的项目引用,但您还尝试在运行时加载该其他程序集,则可能会发生这种情况。

您可以检查程序集是否已在运行时加载,例如

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;

private static void CurrentDomain_AssemblyLoad(object? sender, AssemblyLoadEventArgs args)
{
    var loadedAssemblies = System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies().Select(x => x.FullName);

    if (loadedAssemblies.Contains(args.LoadedAssembly.FullName))
    {
        throw new InvalidOperationException();
    }
}

您可能会也可能不会使用“强命名”解决问题 ~ Assembly loading Problem ("Could not load type")

因此您可以检查程序集,或者在程序集不可用时动态加载。

_assemblyLoadContext = new MyLoadContext((Assembly.GetExecutingAssembly().Location));

var assemblyName = AssemblyLoadContext.GetAssemblyName("MyAssembly.dll");
Assembly assembly = null;

// this is actually "AssemblyName", so still need to resolve to "Assembly" type
var alreadyLoadedAssembly = System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies().FirstOrDefault(x => x.FullName == assemblyName.FullName);

if (object.ReferenceEquals(null, alreadyLoadedAssembly))
{
    assembly = _assemblyLoadContext.LoadFromAssemblyName(assemblyName);
}
else
{
    assembly = System.Reflection.Assembly.GetExecutingAssembly();
}

MyClass1 _classToLoad1 = (MyClass1)assembly.CreateInstance("MyNamespace.MyClass1");
MyClass2 _classToLoad2 = (MyClass2)assembly.CreateInstance("MyNamespace.MyClass2");

关于c# - 用 AssemblyLoadContext 替换 .NET AppDomain,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73972754/

相关文章:

.net - 如何用 .net 中的新字符串替换第 n 个正则表达式组?

c# - System.Enum 作为带有约束的泛型类型参数

.net-core - 如何替换 .NET Standard 的 System.ComponentModel.DataAnnotations 类?

c# - 使用 selenium 和 .NET Core 运行跨浏览器测试

c# - Azure Functions 和 Azure Durable Function 之间有什么区别

c# - 在 C# 中将 Dataset 元素转换为字符串数组

c# - 在 log4net 中使用 smtpAppender 的多个 smtphost 地址

c# - 我应该如何多次插入多条记录?

c# - FileUpload 控件不显示已发布的文件

.net - 如何将 UI Dispatcher 传递给 ViewModel