AssemblyLoadContext 是否隔离静态变量?

ast*_*tef 3 c# static assembly-loading .net-assembly .net-core-3.0

公告告诉我们:

程序集卸载能力是 AssemblyLoadContext 的一项新功能。从 API 的角度来看,这项新功能在很大程度上是透明的,仅通过几个新 API 公开。它允许卸载加载器上下文,释放实例化类型、静态字段和程序集本身的所有内存。应用程序应该能够通过这种机制永远加载和卸载程序集而不会出现内存泄漏。

此外,这个设计说明提到了“静态”。

我试过这个简单的测试:

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

它输出“16”,这意味着隔离不起作用。

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

这样做是否正确,并且可以提供AssemblyLoadContext帮助?

ast*_*tef 5

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

如果我们查看最新的设计说明,我们会看到以下内容:

LoadFromAssemblyName

此方法可用于将程序集加载到与当前正在执行的程序集的加载上下文不同的加载上下文中。程序集将加载到调用该方法的加载上下文中。如果上下文无法在其Load方法中解析程序集,则程序集加载将遵循 默认加载上下文。在这种情况下,加载的程序集可能来自默认上下文,即使该方法是在非默认上下文中调用的。

直接在AssemblyLoadContext.Default上调用此方法将仅从默认上下文加载程序集。根据调用者的不同,默认值可能与当前正在执行的程序集的加载上下文不同,也可能不同。

此方法不会“强制”将程序集加载到指定的上下文中。它基本上启动到指定上下文中指定程序集名称的绑定。该绑定操作将通过完整的绑定解析逻辑,该逻辑可以自由地从任何上下文解析程序集(实际上最可能的结果是指定的上下文或默认的上下文)。上面描述了这个过程。

要确保将指定的程序集加载到指定的加载上下文中,请调用AssemblyLoadContext.LoadFromAssemblyPath并指定程序集文件的路径。

这有点令人沮丧,因为现在我需要确定要加载的程序集的确切位置(没有简单的方法可以“克隆”已加载的程序集)。

此代码有效(输出“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;
}
Run Code Online (Sandbox Code Playgroud)

(注意,现在我们不能强制转换为Proxy类,因为它与proxy变量的运行时类不同,即使是同一个类......)