眾所周知,引用程式集的載入並不是在程式開始運行時就全部載入的,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方法內聯最佳化是被禁止的。