程序集加载和反射,实现了在编译时对一个类型一无所知的情况下,如何在运行时发现类型的信息,创建类型的实例以及访问类型的成员。显现的功能以及效果是十分强大的,比如使用第三方提供的程序集,以及创建动态可扩展应用程序。
程序集加载:
JIT编译器在将方法的IL代码编译成本地代码时,会查看IL代码中引用了哪些类型。在运行时,JIT编译器查看元数据表TypeRef和AssemblyRe,确定对应的程序集。在AssemblyRef表中,包含了构成程序集强名称的各个部分 —名称(无扩展名和路径),版本,语言文化和公钥。(StrongNameDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=07f452de4cf765d5)
JIT编译器利用这些信息,连接成字符串,然后尝试将一个匹配的程序集加载到AppDomain中。如果是弱命名程序集,则只包含程序集的名称。
常见的加载选择有三种:Assembly.Load, Assembly.LoadFrom, Assembly.LoadFile。
(1)Assembly.Load
在内部,CLR使用System.Reflection.Assembly类的静态方法Load来尝试加载程序集,常用的版本原型:
1 public class Assembly
2 {
3 public static Assembly Load(AssemblyName assemblyRef);
4 public static Assembly Load(String assemblyString);
5 }
首先熟悉下这两个方法的使用,创建个强命名程序集,然后查看强命名信息(SN.exe, Reflector工具, Assembly.GetAssemblyName)
1 Assembly assemblyLoadString = Assembly.Load("StrongNameDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=07f452de4cf765d5");
2 Assembly assemblyLoadRef = Assembly.Load(new AssemblyName("StrongNameDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=07f452de4cf765d5"));
注意:
a.对于强命名程序集,在内部,Load导致CLR向程序集应用一个版本绑定重定向策略,并在GAC中查找程序集。如果没有找到,就接着去应用程序的基目录,私有路径子目录和codebase位置中查找
b.如果Load时传递的是一个弱命名程序集,Load就不会向程序集应用一个版本绑定重定向策略,CLR也不会去GAC中查找程序集
c.如果找到指定的程序集,返回的是那个程序集的一个Assembly对象的引用;如果没有找到指定的程序集,抛出System.IO.FileNotFoundException
d.对于需要加载为特定CPU架构生成的程序集,在指定程序集标识时,还可包括一个进程架构部分。
StrongNameDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=07f452de4cf765d5,ProcessorArchitecture=MSIL
CLR允许ProcessorArchitecture取4个值之一:MSIL, x86, IA64, AMD64
(2)Assembly.LoadFrom
使用Load时,它要求你事先掌握构成程序集标识的各个部分。在某些情况下,我们也可以指定一个程序集文件的路径名(包括文件扩展名),获取Assembly对象。
1 Assembly assemblyFromPath = Assembly.LoadFrom(@"E:\StrongNameDLL.dll");
对于使用LoadFrom,传入路径名的使用方式,需要了解内部的实现机制,避免误用的情况:
a.在内部,LoadFrom首先会调用System.Reflection.AssemblyName类的静态方法GetAssemblyName。
该方法打开指定的文件,提取AssemblyRef记录项中的程序集标识信息,然后以一个System.Reflection.AssemblyName对象的形式返回这些信息(文件同时关闭)。
b.LoadFrom方法在内部调用Assembly的Load方法,将AssemblyName对象传给它。
c.CLR会为应用版本绑定重定向策略,并在各个位置查找匹配的程序集。如果Load找到了匹配的程序集,就会加载它,并返回一个Assembly对象;LoadFrom返回这个值。如果没有找到匹配项,LoadFrom就会加载路径名中的程序集。
(3)Assembly.LoadFile
加载指定路径上的程序集文件。
1 Assembly assemblyFromPath = Assembly.LoadFile(@"E:\StrongNameDLL.dll");
注意:
a.可将具有相同标识的一个程序集多次加载到一个AppDomain中
b.CLR不会解析任何依赖性问题
c.必须向AppDomain的AssemblyResolve事件登记,并让事件回调方法显式地加载任何依赖的程序集
三者对比
既然已经对Load,LoadFrom,LoadFile有所了解,那么接着来看看这三者之间的区别与对比。
1.Load和LoadFrom
a.根据LoadFrom的内部实现机制,LoadFrom返回的实际是Load找到的匹配程序集的一个Assembly对象(在找到匹配的程序集的情况下)
b.LoadFrom存在多次打开文件的现象,并且内部还是要走一套Load的逻辑操作,还存在对比情况,所以LoadFrom比Load效率低
c.LoadFrom要求指定路径中包含 FileIOPermissionAccess.Read 和 FileIOPermissionAccess.PathDiscovery 或 WebPermission
d.LoadFrom, and the probing path includes an assembly with the same identity but a different location, an InvalidCastException, MissingMethodException, or other unexpected behavior can occur. ‘ data-guid="750168c3a98c2f6f128b792974715bf7">如果用LoadFrom加载一个程序集,并且probing路径包括具有相同标识但位置不同的程序集,则发生InvalidCastException、MissingMethodException或其他意外行为。
2.LoadFrom和LoadFile
a.根据MSDN的解释,可以看出功能实现机制存在区别。
LoadFrom:已知程序集的文件名或路径,加载程序集。
LoadFile:加载指定路径上的程序集文件的内容。
b.LoadFrom会加载程序集的依赖项,但是LoadFile不会。
例如,上面的StrongName.dll存在引用程序集ReferenceDemo.dll。使用LoadFrom,StrongName.dll和ReferenceDemo.dll都会被载入,但是使用LoadFile,ReferenceDemo.dll不会被载入。
c.可以使用LoadFile加载并检查具有相同标识但位于不同路径的程序集,但是LoadFrom不行,LoadFrom returns the loaded assembly even if a different path was specified. ‘ data-guid="3b5239827d63ca32b5eed3a6256fca18">如果已加载一个具有相同标识的程序集,则即使指定了不同的路径,仍返回已加载的程序集。。
例如,StrongName.dll存在于两个不同路径下,LoadFile可以正确载入两个程序集;但是LoadFrom如果已经加载了一次StrongName.dll,再一次加载不同路径下的Assembly时,会先检查前面是否已经载入过相同名字的Assembly,最终返回的还是第一次加载的Assembly对象的引用。
总结:
由于LoadFrom具有不少缺点,一般优先考虑使用Load方法加载程序集。而LoadFile则只在有限的情况下使用。