C#插件式开发——详解默认ALC和当前ALC的区别(AssemblyLoadContext)

news/2024/11/28 19:04:17/

默认ALC(默认ALC)

当一个应用程序启动时,CLR将一个特殊的ALC分配给静态的Assembly LoadContext.Default属性。

加载启动程序集及其静态引用的依赖项和 .NET 运行时 BCL 程序集都是在默认 ALC 中完成的。

默认 ALC 首先在默认探测路径(default probing paths)中查找以自动解析程序集。默认探测路径 通常是应用程序的 .deps.json 和 .runtimeconfig.json 文件中指示的位置。

如果 ALC 在其默认探测路径中找不到程序集,则会触发其 Resolving 事件。你可以通过处理此事件去从其他位置加载程序集,您也可以将应用程序的依赖项部署到其他位置,例如子文件夹、共享文件夹,甚至作为主机程序集中的二进制资源:

AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) =>
{// Try to locate assemblyName, returning an Assembly object or null.// Typically you’d call LoadFromAssemblyPath after finding the file.// ...
};

当自定义 ALC 无法解析时(换句话说,当其 Load 方法返回 null 时)并且默认 ALC 无法解析程序集时,默认 ALC 中的 Resolving 事件也会触发。

你也可以不通过解析事件(Resolving event)将程序集加载到默认ALC。然而,在继续之前,你应该首先确定你是否可以通过使用一个单独的ALC或使用我们在下一节中描述的方法(使用执行和上下文ALC)来更好地解决问题。对默认 ALC 进行硬编码会使您的代码变得脆弱,因为它不能作为一个整体被隔离(例如,通过单元测试框架或 LINQPad)。

如果您仍想继续,最好调用解析方法(即 LoadFromAssemblyName)而不是加载方法(例如 LoadFromAssemblyPath)——尤其是当您的程序集被静态引用时。这是因为程序集可能已经加载,在这种情况下 LoadFromAssemblyName 将返回已加载的程序集,而 LoadFromAssemblyPath 将抛出异常。

(使用LoadFromAssemblyPath,你也有可能从一个与ALC的默认解析机制不一致的地方加载程序集的风险。)

如果程序集位于 ALC 不会自动找到它的地方,您仍然可以按照此过程并额外处理 ALC 的 Resolving 事件。

注意,在调用 LoadFromAssemblyName 时,无需提供全名;简单的名称就可以了(即使程序集被强命名也是有效的):

AssemblyLoadContext.Default.LoadFromAssemblyName ("System.Xml");

但是,如果您在名称中包含公钥令牌,它必须与加载的内容相匹配。

默认探测(Default probing)

默认探测路径通常包含以下几个:

  • AppName.deps.json中指定的路径(其中AppName是你的应用程序的主程序集的名称)。如果这个文件不存在,就会使用应用程序的根目录来代替。
  • 包含.NET运行时系统程序集的文件夹(如果你的应用程序的部署模式是依赖框架的)。

MSBuild 自动生成一个名为 AppName.deps.json 的文件,它描述了在哪里可以找到它的所有依赖项。

其中包括放置在应用程序根目录下的与平台无关的程序集,以及放置在 win 或 unix 等子文件夹下的 runtimes\ 子目录中的特定于平台的程序集。

在生成的 .deps.json 文件中指定的路径是相对于应用程序根目录的——或者您在 AppName.runtimeconfig.json or AppName.runtimeconfig.dev.json 配置文件的 additionalProbingPaths 部分中指定的任何其他文件夹(后者仅用于开发环境)

当前ALC(“Current” ALC)

在上一节中,我们告诫不要将程序集显式加载到默认 ALC 中。相反,您通常想要的是加载/解析到“当前”ALC。

在大多数情况下,“当前” ALC是包含当前正在执行程序集的ALC:

var executingAssem = Assembly.GetExecutingAssembly();
var alc = AssemblyLoadContext.GetLoadContext (executingAssem);
Assembly assem = alc.LoadFromAssemblyName (...); // to resolve by name// OR: = alc.LoadFromAssemblyPath (...); // to load by path

获取ALC有一种更灵活,更明确的方法:

var myAssem = typeof (SomeTypeInMyAssembly).Assembly; 
var alc = AssemblyLoadContext.GetLoadContext (myAssem);
...

有时,无法推断出 "当前 "的ALC。例如,假设您负责编写 .NET 二进制序列化程序,在这个程序中你写入它序列化的类型的全名(包括它们的程序集名称),然后这些名称必须在反序列化期间解析。问题是,应该使用哪种 ALC来解析?而依赖当前执行程序集的问题是,它将返回任何包含反序列化器的程序集,而不是调用反序列化器的程序集。

最好的解决办法明确是哪个ALC:

public object Deserialize (Stream stream, AssemblyLoadContext alc) {...
}

明确的方式可以最大限度地提高灵活性,最大限度地减少犯错的机会:

var assem = typeof (SomeTypeThatIWillBeDeserializing).Assembly; 
var alc = AssemblyLoadContext.GetLoadContext (assem); 
var object = Deserialize (someStream, alc);

Assembly.Load and 上下文 ALCs

下列展示将一个Assembly加载到当前执行的ALC:

var executingAssem = Assembly.GetExecutingAssembly(); 
var alc = AssemblyLoadContext.GetLoadContext (executingAssem); 
Assembly assem = alc.LoadFromAssemblyName (...); 

微软在Assembly类中定义了以下方法:

public static Assembly Load (string assemblyString)

以及一个接受AssemblyName作为参数的重载版本:

public static Assembly Load (AssemblyName assemblyRef);

(不要将这些方法与传统的 Load(byte[]) 方法混淆,后者的行为方式完全不同,将在下面介绍)

与 LoadFromAssemblyName 一样,您可以选择指定程序集的简单名称、部分名称或全名:

Assembly a = Assembly.Load ("System.Private.Xml");

这会将 System.Private.Xml 程序集加载到正在执行代码程序集的任何 ALC 中。

在这种情况下,我们指定了一个简单的名字。以下参数也是有效的,且在.NET中都会有相同的结果

"System.Private.Xml, PublicKeyToken=cc7b13ffcd2ddd51""System.Private.Xml, Version=4.0.1.0""System.Private.Xml, Version=4.0.1.0, PublicKeyToken=cc7b13ffcd2ddd51"

如果要指定一个公钥令牌,那么它必须与加载的程序集相匹配。

这两个方法都是严格意义上的解析,所以不能指定一个文件路径。(如果你在AssemblyName对象中填入CodeBase属性,会被忽略掉)。

自定义Assembly.Load

如果你想自定义一个Assembly.Load,可以参考下面的模板:

[MethodImpl(MethodImplOptions.NoInlining)]
Assembly Load (string name)
{Assembly callingAssembly = Assembly.GetCallingAssembly();var callingAlc = AssemblyLoadContext.GetLoadContext (callingAssembly);return callingAlc.LoadFromAssemblyName (new AssemblyName (name));
}

EnterContextualReflection

当Assembly.Load通过中间人(如反序列化器或单元测试运行器)被调用时,Assembly.Load使用调用者程序集的ALC上下文的策略将会失败。

如果中间人被定义在一个不同的程序集中,那么中间人的加载上下文被使用,而不是调用者的加载上下文。

为了让Assembly.Load在这种情况下仍能工作,微软为AssemblyLoadContext增加了一个方法,叫做EnterContextualReflection。它会为AssemblyLoadContext.CurrentContextualReflectionContext分配一个ALC。虽然这是一个静态属性,但它的值存储在一个 AsyncLocal 变量中,因此它可以在不同的线程上保存不同的值(但在整个异步操作中仍会保留)。

如果此属性非空,Assembly.Load 会自动优先使用它而不是调用方的ALC。

Method1();
var myALC = new AssemblyLoadContext ("test");
using (myALC.EnterContextualReflection()) {Console.WriteLine (AssemblyLoadContext.CurrentContextualReflectionContext.Name); // testMethod2();
}
// Once disposed, EnterContextualReflection() no longer has an effect.
Method3();
void Method1() => Assembly.Load ("..."); // Will use calling ALC
void Method2() => Assembly.Load ("..."); // Will use myALC
void Method3() => Assembly.Load ("..."); // Will use calling ALC

我们之前演示了如何编写和Assembly.Load功能类似的方法。下面给出更准确的版本,它考虑了上下文中的反射上下文(contextual reflection context)的场景:

[MethodImpl(MethodImplOptions.NoInlining)]
Assembly Load (string name)
{var alc = AssemblyLoadContext.CurrentContextualReflectionContext?? AssemblyLoadContext.GetLoadContext (Assembly.GetCallingAssembly());return alc.LoadFromAssemblyName (new AssemblyName (name));
}

尽管上下文中的反射上下文背景下(contextual reflection context)在允许遗留代码运行方面很有用,但更健壮的解决方案(如我们之前所述)是修改调用 Assembly.Load 的代码,使其在调用者传递进来的ALC上调用LoadFromAssemblyName。


http://www.ppmy.cn/news/545996.html

相关文章

黑苹果alc269声卡仿冒id_10.10中我的ALC269VC依旧无声?学习并尝试制作了仿冒声卡,依然无声。...

请各位帮忙看一下我电脑的情况。下面有相关的截图和照片。哪位大大可以帮忙修改一下不? 问题有3个: 1、其中V图中显示的声卡信息ID为【8086:1e20】,这个地址是集成显卡中的HDMI音频的驱动吗?ALC269VC的硬件ID应该为10EC0269才对的…

ALC--实验报告

本次实验拓扑图如下 实验要求如图 步骤: 1.网络拓扑图搭建 2.配置路由器和交换机的IP地址和端口地址 3.ping通四台设备 4. 配置命令: 标准列表 ---由于其仅关注数据包中的源ip地址,故调用位置应该尽量靠近目标,避免误删; …

【Java】以数组为例简单理解引用类型变量

我们首先要知道内存是一段连续的存储空间,主要用来存储程序运行时数据的,如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦,就像一个杂乱的房间,你如果想在这个房间里找一个东西的话,找起来就会…

推荐一个桌面整理小工具

试了很多的桌面整理工具,网上那些排行简直了,纯粹是做广告,那么难用竟然还排前几。经试用,觉得金山旗下的最好用,简洁大方,用起来特顺手,还特别方便换桌面背景。 下载地址:http://ww…

好用的桌面整理工具

个人最近用了腾讯桌面整理工具(独立版),感觉还是挺不错的,很简单,像我个人就比较简介的这种桌面工具,可以提高一定的工作的效率,新手推荐呢。

桌面管理,Windows自带工具!很强!

这两天看到自己桌面上的快捷方式图标越来越多,真是折磨,有时候找个软件找半天(悄悄bb一句,主要是挡住我的壁纸了),真的是很令人难受!就想着整理一下桌面,如下是用windows自带的工具整…

uTools:一款免费的桌面神器

前言 相信很多人在使用电脑的时候都会使用搜索功能,或者频繁的打开我的电脑,进行文件查找,有时候忘记文件的位置,一翻就是半天,电脑安装非常多的软件,桌面上密密麻麻都是软件图标,方便启动。亦…

桌面管理 | Windows自带工具

1、话不多说,先上图 看到这张图是否就像看到了你的桌面?甚至是你的桌面比这个还乱。桌面的快捷方式越来越多,各种文件夹以及文件等杂乱不堪的摆在桌面,真是折磨。有时候,你找个想要的软件或者文件要找半天&#xff0…