用 AssemblyLoadContexts 替换 .NET AppDomains

eri*_*icc 6 .net c# .net-core .net-6.0

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

通过阅读文档和 .NET 博客,我了解到 AppDomains 从 .NET Core 或更高版本开始已停用,以隔离方式加载程序集的正确方法是使用AssemblyLoadContexts。因此,我用自己的子类覆盖了 AssemblyLoadContext,并使用它来加载库的程序集,使用这些实例实例化其类型Assembly.CreateInstance并使用这些实例来加载第三方程序集,正如我在网上阅读的内容表明这是确保最干净的方法您正在将程序集加载到与您的代码本身不同的上下文中。

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

这是我的 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;

        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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;

            
        }
        

    }


}
Run Code Online (Sandbox Code Playgroud)

当我尝试将myClassObj1和转换myClassObj2MyClass1and的实例时MyClass2,它会抛出 InvalidCastException,因为对象驻留在AssemblyLoadContext与执行转换的代码不同的位置。

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

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

Bur*_*sBA 2

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

类型是每个组件的;如果“相同”程序集加载了两次,则该程序集的每个“副本”中的类型不被视为相同类型。

/sf/answers/175057431/

很难说,但我从您的代码中认为您尝试加载的程序集已经加载。否则,您尝试转换的类型将不存在。(旁注,由于类型是已知的,您可以跳过它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();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可能会或可能不会使用“强命名”来解决问题〜程序集加载问题(“无法加载类型”)

因此,您可以检查程序集,或者动态加载(如果它尚不可用)。

_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");
Run Code Online (Sandbox Code Playgroud)