众所周知,引用程序集的加载并不是在程序开始运行时就全部加载的,CLR会加载相应的程序集当该程序集的类型被第一次使用。更具体的说,当一个方法被JIT时,CLR会确保该方法中的类型所在的程序集被加载。
比如我们的主程序Mgen.exe引用一个类库ClassLibrary1,后者有一个类型Class1:
这段代码,分别在使用ClassLibrary1的一个类型之前对当前应用程序域加载的程序集进行枚举:
static void Main()
{
foreach (var ass in AppDomain.CurrentDomain.GetAssemblies())
Console.WriteLine(ass.GetName().Name);
Console.WriteLine("=== 分割线 ===");
//使用ClassLibrary1的类型
doo();
foreach (var ass in AppDomain.CurrentDomain.GetAssemblies())
Console.WriteLine(ass.GetName().Name);
}
static void doo()
{
var obj = new ClassLibrary1.Class1();
}
运行代码(注意要在Release发布模式),程序会输出:
mscorlib
Mgen
ClassLibrary1
=== 分割线 ===
mscorlib
Mgen
ClassLibrary1
为何ClassLibrary1出现在第一个列表中?事实上如果JIT不对doo进行内联处理的话,当Main方法被JIT后,CLR是不会觉察到Main方法会使用ClassLibrary1的,因此ClassLibrary1是不会出现在列表中的,但运行时刻JIT的优化使得doo被内联在Main方法中,这样整个代码相当于:
static void Main()
{
//省略
//内联的doo
var obj = new ClassLibrary1.Class1();
//省略
}
使用MethodImplOptions.NoInlining可以阻止JIT在编译时把某些方法进行内联处理。注意MethodImpleOptions枚举要用在MethodImpl特性上。(另外MethodImpleOptions枚举还有其他功能,比如常见的MethodImpleOptions.Synchronized用来进行线程同步。可以参考MSDN:http://msdn.microsoft.com/zh-cn/library/system.runtime.compilerservices.methodimploptions.aspx)
最后注意上述类都在System.Runtime.CompilerServices命名空间内。
这样的话在doo方法上加入MethodImpl特性:
//+ System.Runtime.CompilerServices;
[MethodImpl(MethodImplOptions.NoInlining)]
static void doo()
{
var obj = new ClassLibrary1.Class1();
}
再次编译整个程序,输出:
mscorlib
Mgen
=== 分割线 ===
mscorlib
Mgen
ClassLibrary1
可以看到,此时由于doo没有被内联,JIT编译Main方法后没有发现使用其他程序集,所以第一次枚举程序集没有ClassLibrary1,而当doo方法真正执行后,更确切的说是在doo方法被JIT后,但在执行前,CLR会加载ClassLibrary1。所以第二次枚举应用程序域中的程序集时,ClassLibrary1在列表中。
如果在Debug调试模式下运行程序,结果也是上面的输出(ClassLibrary1不在列表1),因为调试模式下,JIT方法内联优化是被禁止的。