I'm trying to upgrade a .NET Framework class library to .NET 6 and have run into some issues around AppDomains. I didn't write the library originally, but my understanding is that it creates an AppDomain separate from the one the .NET runtime created for it, instantiates some of its own types using this new app domain by calling AppDomain.CreateInstanceAndUnwrap
and those instances then load other third-party assemblies for inspection. I presume the reason it instantiated its own types in this way was to completely isolate the code processing the third-party assemblies and those assemblies themselves from the library's execution context.
Reading documentation and .NET blogs, I've learned that AppDomains were retired as of .NET Core or later, and the correct way to load assemblies in an isolated fashion is to use AssemblyLoadContext
s. I've therefore overridden AssemblyLoadContext with my own subclass, and used that to load the library's assembly, instantating its types using Assembly.CreateInstance
and using those instances to load third-party assemblies, as what I've read online suggests this is the cleanest way to ensure you're loading assemblies into a context separate to your code's own.
There are a couple of uncertainties around this approach - I'm not sure if it's still necessary to instantiate our library's types in a separate context. If it is, I have a problem around casting the instances from System.Object
returned by Assembly.CreateInstance
to their correct original types.
Here is my AssembyLoadContext derivative:
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;
}
}
}
Here is my class which tries to other load types from the same assembly as the class:
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;
}
}
}
When I try and cast myClassObj1
and myClassObj2
to instances of MyClass1
and MyClass2
, it throws an InvalidCastException because the objects reside in a different AssemblyLoadContext
to the code performing the cast.
I've read that the .NET runtime treats types created in different AssemblyLoadContexts as unequal, so the cast fails and assigns null to variables with the correct type, even though they came from the same assembly and have the same name and implementation. I'm not sure how to address this issue, although I assign the result of Assembly.Create instance to a dynamic
and invoke that, which seems like a hack.
I read one way is to externalise the types being cast into their own assembly, but this seems like overkill and would mean extra overhead managing yet another library. I also don't know why this wasn't a problem when using separate AppDomains which achieve the same thing conceptually.