AssemblyLoadContext 是否隔离静态变量?

Does AssemblyLoadContext isolate static variables?

Announcement 告诉我们:

Assembly unloadability is a new capability of AssemblyLoadContext. This new feature is largely transparent from an API perspective, exposed with just a few new APIs. It enables a loader context to be unloaded, releasing all memory for instantiated types, static fields and for the assembly itself. An application should be able to load and unload assemblies via this mechanism forever without experiencing a memory leak.

另外,这个 design notes 提到了 "statics"。

我试过这个简单的测试:

static void Main()
{
    Proxy.X = 15;
    var alc = new AssemblyLoadContext("MyTest", true);
    var asm = alc.LoadFromAssemblyName(typeof(Program).Assembly.GetName());
    var proxy = (Proxy)asm.CreateInstance(typeof(Proxy).FullName);
    Console.WriteLine(proxy.Increment());
}

class Proxy
{
    public static int X;
    public int Increment() => ++X;
}

输出“16”,表示隔离不起作用

我的目标是单元测试 class 可能抛出异常的静态成员。通常的测试可以通过触发类型初始值设定项来影响彼此的行为,因此我需要以最便宜的方式隔离它们。测试应该 运行 在 .NET Core 3.0 上。

这样做是否正确,AssemblyLoadContext 可以帮忙吗?

是的,它确实隔离了静态变量。

如果我们查看 newest design notes,我们会看到这个添加:

LoadFromAssemblyName

This method can be used to load an assembly into a load context different from the load context of the currently executing assembly. The assembly will be loaded into the load context on which the method is called. If the context can't resolve the assembly in its Load method the assembly loading will defer to the Default load context. In such case it's possible the loaded assembly is from the Default context even though the method was called on a non-default context.

Calling this method directly on the AssemblyLoadContext.Default will only load the assembly from the Default context. Depending on the caller the Default may or may not be different from the load context of the currently executing assembly.

This method does not "forcefully" load the assembly into the specified context. It basically initiates a bind to the specified assembly name on the specified context. That bind operation will go through the full binding resolution logic which is free to resolve the assembly from any context (in reality the most likely outcome is either the specified context or the default context). This process is described above.

To make sure a specified assembly is loaded into the specified load context call AssemblyLoadContext.LoadFromAssemblyPath and specify the path to the assembly file.

这有点令人沮丧,因为现在我需要确定要加载的程序集的确切位置(要 "clone" 已经加载的程序集没有简单的方法)。

此代码有效(输出“1”):

static void Main()
{
    Proxy.X = 15;
    var alc = new AssemblyLoadContext("MyTest", true);
    var asm = alc.LoadFromAssemblyPath(typeof(Program).Assembly.Location);
    var proxy = asm.CreateInstance(typeof(Proxy).FullName);
    Console.WriteLine(proxy.GetType().GetMethod("Increment").Invoke(null, null));
}

class Proxy
{
    public static int X;
    public static int Increment() => ++X;
}

(注意,现在我们不能转换为Proxy class,因为它不同于运行-time class proxy 变量,即使相同 class...)