.NET:鲜为人知的 “Load Context”
背景
任何一门语言都要了解其类型加载过程,如:Java 的 Class Loader,NodeJS 的搜索方式等,本文概述一下我对 CLR 如何加载程序集,重点说一下 Load Context。
其编译时只是在程序集中生成了元数据(如:依赖的程序集标识)和代码。当代码执行时,CLR 会根据元数据加载依赖的程序集。
Load Context
参考文章:http://msdn.microsoft.com/en-us/library/dd153782(v=vs.110).aspx。
Assembly 会被加载到三个 Load Context 中的任意一个,或者没有在任何上下文中,这三个 Load Context 的名字叫:Default Context、Load-From Context 和 Relfection-Only Context,另外一个没有在任何上下文,可以叫:No Context。
每一个 Load Context 都有自己加载依赖程序集的规则,在解释每个 Load Context 解析依赖的规则之前,先看一下 CLR 的探测过程。
CLR 探测过程
弱签名程序集的探测过程
代码
Console.WriteLine(Type.GetType("B.ClassB, B"));
文化中立程序集的探测优先级
BaseDirectory\B.dll
BaseDirectory\B\B.dll
BaseDirectory\B.exe
BaseDirectory\B\B.exe
文化相关程序集的探测优先级
BaseDirectory\zh-CN\B.dll
BaseDirectory\B\zh-CN\B.dll
BaseDirectory\zh-CN\B.exe
BaseDirectory\B\zh-CN\B.exe
使用 probing 指定 privatePath
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="B_" />
</assemblyBinding>
</runtime>
</configuration>
指定 privatePath 后的程序集探测优先级
BaseDirectory\B.dll
BaseDirectory\B\B.dll
BaseDirectory\_B\B.dll
BaseDirectory\_B\B\B.dll
BaseDirectory\B.exe
BaseDirectory\B\B.exe
BaseDirectory\_B\B.exe
BaseDirectory\_B\B\B.exe
注:pirvatePath 只能定义为 BaseDirectory 的子目录。
注:上例只是列出了文化中立程序集的探测优先级,文化相关程序集的探测优先级应该不难猜测出来。
注:也可以使用 codeBase 配置元素来指定探测的子目录,这里就不介绍了。
强签名程序集的探测过程
强签名程序集会先探测 GAC,如果在 GAC 中没有找到,就按照弱签名程序集的探测过程执行探测。
注:在探测之前 CLR 会根据应用程序配置文件、发布者策略文件和全局配置文件中的配置获取重定向后的版本,然后使用这个重定向后的版本执行探测。
注:强签名程序集的 codeBase 可以指定任意目录(包括 Web 地址)。
Default Context
使用探测过程加载的程序集都会加载在 Default Context 中,Default Context 中的程序集具备如下特点:
- 依赖的程序集会使用探测过程被自动加载到 DefaultContext。
- 只包含探测过程可以探测到的程序集。
- 其它 Load Context 加载的程序集对于探测过程是不可用的。
Load-From Context
使用 Assembly.LoadFrom 或 Assembly.Load 的某些重载方法加载的程序集时,如果参数使用了基于路径的形式,而非基于标识的形式,那么加载的程序集会加载在 Load-From Context 中。
Load-From Context 中的程序集具备如下特点:
- 如果 Load-From Context 中已经包含了要加载的程序集(标识相同),就不会加载两次,即使两次加载的路径不同,下面的例子输出的数值是一样的:
Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G_\G.dll");
PrintAssemlyCount();
Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G__\G.dll");
PrintAssemlyCount(); - Load-From Context 中的程序集对于探测过程是不可用的,下面的例子输出为 null:
Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G_\G.dll");
Console.WriteLine(Type.GetType("G.ClassG, G"));注:G.DLL 不要在探测路径上。
- Load-From Context 和 Default Context(或者其它Context)都可以同时加载相同标识的程序集,但是程序集内的类型已经不是相同的类型了(虽然内容一样 ),如下例输出为 false:
var ass = Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G_\G.dll");
Console.WriteLine(Type.GetType("G.ClassG, G") == ass.GetTypes().First(x => x.Name == "ClassG"));注:G.DLL 不要在探测路径上。
- 依赖的程序集会自动加载,可以从 Default Context 加载,也可以从 Load-Form Context 维护的 Path 下加载,算法后面单独分析,见下例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace H
{
public class ClassH
{
public ClassH()
{
Console.WriteLine("自动加载A:" + Type.GetType("A.ClassA, A").Assembly.Location);
Console.WriteLine("自动加载I:" + Type.GetType("I.ClassI, I").Assembly.Location);
}
}
}
在 Main 中执行如下代码:
var hAss = Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\H\bin\Debug\H.dll"); Activator.CreateInstance(hAss.GetTypes().First(x => x.Name == "ClassH"));
注:上例中 ClassH 依赖的两个类型都得到正常的加载了。
Load-From Context 中的程序集的依赖程序集的加载算法如下:

注:依赖的程序集的依赖的程序集的算法同上。
No Context
通过 Assembly.LoadFile 或 Assembly.Load(byte[]) 形式加载的程序集会加载在 No Context 中,No Context 中的程序集具备如下特点:
- 依赖的程序集会自动加载,只会从 Default Context 加载,可以监听这个事件:AppDomain.CurrentDomain.AssemblyResolve,在事件监听器里手工加载其它依赖。
下面这个例子会出现异常:Assembly
.LoadFile(@"E:\Coding\HappyStudy\LoadContextStudy\Test\bin\Debug\Plugs\Implements\1.0.0.0\Contracts.dll");
var operatorType = Assembly
.LoadFile(@"E:\Coding\HappyStudy\LoadContextStudy\Test\bin\Debug\Plugs\Implements\1.0.0.0\Implements.dll")
.GetTypes()
.First(x => x.Name == "Operator");
var operatorInstance = Activator.CreateInstance(operatorType);注:Implements.dll 依赖 Contracts.dll。如果 Contracts.dll 包含在探测路径中,则程序会正常执行,不过会加载两个 Contracts.dll。
- 只要路径不同,会返回多个相同程序集标识的程序集实例,如下例:
Assembly.LoadFile(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\F_\F.dll");
PrintAssemlyCount();
Assembly.LoadFile(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\F__\F.dll");
PrintAssemlyCount();
Relfection-Only Context
只有开发工具是才会用到,这里就不介绍了。
参考资料
- Programming with Application Domains and Assemblies。
- How the Runtime Locates Assemblies。
- Best Practices for Assembly Loading。
备注
说了挺多的,有啥用处呢?其实 Load Context + AppDomain.CurrentDomain.AssemblyResolve 可以模拟 Java 的 ClassLoader,也可以进一步的模拟 OSGI,下篇文章写个简单的 Demo,不过微软不推荐用这种方式,推荐使用 AppDomain。
.NET:鲜为人知的 “Load Context”的更多相关文章
- Load ContextCLR 探测
目录 背景Load ContextCLR 探测过程弱签名程序集的探测过程强签名程序集的探测过程Default ContextLoad-From ContextNo ContextRelfection- ...
- Assembly中Load, LoadFrom, LoadFile以及AppDomain, Activator类中相应函数的区别
Assembly和AppDomain的一些关于动态加载程序集的函数有些令人头疼,但细细研究后还是可以将他们区分的. 这些函数大致可以分为四类: 第一类:加载到Load Context内 Load Co ...
- Could not load file or assembly 'System.Web.Http
使用FusLogVw https://stackoverflow.com/questions/4469929/could-not-load-file-or-assembly-or-one-of-its ...
- 分享个 之前写好的 android 文件流缓存类,专门处理 ArrayList、bean。
转载麻烦声明出处:http://www.cnblogs.com/linguanh/ 目录: 1,前序 2,作用 3,特点 4,代码 1,前序 在开发过程中,client 和 server 数据交流一 ...
- EF里单个实体的增查改删以及主从表关联数据的各种增删 改查
本文目录 EF对单个实体的增查改删 增加单个实体 查询单个实体 修改单个实体 删除单个实体 EF里主从表关联数据的各种增删改查 增加(增加从表数据.增加主从表数据) 查询(根据主表找从表数据.根据从表 ...
- Notepad++源码编译及其分析
Notepad++是一个小巧精悍的编辑器,其使用方法我就不多说了,由于notepad++是使用c++封装的windows句柄以及api来实现的,因此对于其源码的研究有助于学习如何封装自己简单的库(当然 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (28) ------ 第五章 加载实体和导航属性之测试实体是否加载与显式加载关联实体
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-11 测试实体引用或实体集合是否加载 问题 你想测试关联实体或实体集合是否已经 ...
- spring boot源码分析之SpringApplication
spring boot提供了sample程序,学习spring boot之前先跑一个最简单的示例: /* * Copyright 2012-2016 the original author or au ...
- Spring Boot启动流程详解(一)
环境 本文基于Spring Boot版本1.3.3, 使用了spring-boot-starter-web. 配置完成后,编写了代码如下: @SpringBootApplication public ...
随机推荐
- 【转载】pygame的斜线运动
pygame是用来写2D游戏的. 实现斜线运动,无非是在x和y两个方向上分运动的合成.x方向上的运动,当撞到边界的时候取相反速度就好了. 这里是用网球王子中的图片,以及一个网球实现,效果截图: 注意看 ...
- MIT6.006Lec01:Python实现
MIT6.006是Algo Intro这门课,据说语言使用python Lec01是讲peak finding,也就是峰值点 具体为: 一维情况下一个数组中a[i]>a[i-1]且a[i]> ...
- Aspose.Words 自定义文档模版生成操作类
/// <summary> /// 操作word通用类 LIYOUMING add 2017-12-27 /// </summary> public class DocHelp ...
- android拾遗——Android Intent详解
一. Intent 作用 Intent 是一个将要执行的动作的抽象的描述,一般来说是作为参数来使用,由Intent来协助完成android各个组件之间的通讯.比如说调用startActivity()来 ...
- 东莞裕同&易普优APS项目启动啦!
2018年6月21日,东莞裕同&易普优APS项目启动会在东莞裕同东城厂区正式召开.裕同东莞副总经理李总.PMC张经理.集团信息中心曾总.罗经理.易普优实施总监陈总.曹经理等参加了此次会议.这是 ...
- loadrunner 脚本中文乱码
loadrunner 脚本中文乱码 1.新建脚本--->选择协议(Http)-->选项-->高级-->选择“支持字符集”并点选“UTF-8”: 2.在回放脚本之前:Vuser- ...
- 8-16 不无聊序列 non-boring sequences uva1608
题意: 如果一个序列的任意连续子序列中至少有一个只出现一次的元素 则称这个序列是 不无聊序列 输入一个n个元素的序列a 判断是不是不无聊序列 一开始想当然 以为只要 2位的子序列都满足即可 ...
- 【小技巧】限制windows server 2008的最大用户登录数
把云服务器单纯当作自己一个云端主机的人大有人在.本人就是其中一位. 由于windows server 2008的会话保持机制,导致你关闭掉当前远程桌面连接,并从另外一台电脑上开启远程连接之后,另外一台 ...
- rabbitmq学习(二) —— helloword!
rabbitmq学习当然是跟着官网走最好了,官网的教程写的很好,跟着官网教程走一遍就会有个初步了解了 下面的教程转自http://cmsblogs.com/?p=2768,该博客对官网的翻译还不错 介 ...
- Java 中的异常处理机制
生活中的异常: 不能够完整而顺利的完成一些工作 根据不同的异常进行相应的处理,而不会就此终端我们的生活 引出: 异常处理: 方式: 1.选择结构(逻辑判断)避免 demo:if逻辑处理异常 im ...