《CLR via C#》第四版
為什麼有時候有JIT的語言會比直接編譯為機器碼的語言快?
簡而言之,就是JIT所知道的信息比那些在開發機上直接編譯為機器碼的的編譯器知道的信息更多,有的時候這些信息是如此的有用,以至於效果可以超過JIT本身的開銷和JIT編譯時間受限帶來的限制。《CLR》給出了如下三種具體的原因:
除此以外,我認為還有一個重要的原因是:
JIT可以跨越程序集文件的邊界來進行優化、內聯,這些程序集在編譯生成的時候可能是在不同的時間、不同的機器上,傳統編譯器對此無能為力。
checked 關鍵字的作用范圍僅在當前所在函數內,不影響checked塊中調用的函數,所以下面這段代碼不會拋異常
Main( a = +=
其實想想也正常,checked關鍵字的作用是將數值運算編譯為帶檢查的IL指令,如果調用的函數在另一個程序集中,該程序集早已被編譯好,又如何改變呢?
但注意:Decimal並非基本類型,四則運算沒有IL指令對應,所以不受checked影響,其運算始終會拋異常。
值類型表示不會額外為此對象在對上分配,而值類型自己可能被包含在一個引用類型中,所以值類型未必不會在堆上。
值類型也用new關鍵字,容易給人造成誤解。
值類型可以通過顯示指定將多個值類型的字段重疊在一起。
只有C++/CLI才能獲得指向已裝箱的值類型的指針,C#只能先拆箱。
當作為模板的類型參數時,值類型會強制CLR為它專門生成一份特化的代碼,而只有引用類型的模板實例可以共享代碼,減少內存占用。
個人經驗:
引用類型new一次只有一個實例,而值類型則未必,當值類型被傳遞和修改,其行為需要仔細分析各個值類型變量的生存期,給開發人員帶來不小的負擔,這裡面包括所謂的裝箱拆箱。建議只在局部范圍內或是作為只讀對象的情況下才考慮使用,因為C#碼農普遍沒有C++碼農那樣對對象生存期有明確的把握的能力,容易被豬隊友害死。
在GC回收時,某個對象即使沒超出C++意義上的生存范圍(所在的塊),但由於在下面未運行的代碼中沒有被引用所以一樣會被認為沒有被引用而被GC。在/debug模式下,對象生存期會延長到函數體結束。
Timer t = Timer(TimerCallback, , , Console.WriteLine( + DateTime.Now);
在進程正常結束的時候CLR也會執行GC過程,並釋放對象。
對於非托管的資源,建議使用SafeHandle系列管理其句柄,其基類CriticalFinalizerObject有如下CLR級別支持的額外特性:
此外:
dynamic類型被處理為Object+DynamicAttribute,所以不能通過Object和dynamic來實現不同的重載。
const的值會在編譯時被內聯,readonly則不會,所以未來可能需要改動的值不應該用const。
Nullable類型在裝箱時CLR會特殊處理,脫掉Nullable,即null被裝箱為null,v被裝箱為v,而不是Nullable<V>類型。
可以通過AppDomain的FirstChanceException事件監視異常被拋出,但事件回調函數不能處理這個異常。
如果一個異常沒有被CLR處理,被報告至Windows Error Reporting,那麼它獲得的調用棧只能到最近一次被throw或是re-throw的位置,即re-throw對Windows Error Reporting無效,仍然會重置異常拋出點。
在Catch和Finally塊中,線程不會被Abort所中斷.
Environment.FailFast可以跳過普通的異常處理邏輯和對象Finalize方法直接結束進程。
使用反射調用時,如果拋出異常,會將該異常包裹為TargetInvocationException;dynamic不受此影響。
Constrained Execution Regions (CERs),該功能可以讓CLR預先在try塊之前“准備”一段代碼,而不是在運行過程中由於載入DLL失敗、類靜態初始化失敗等原因拋出異常。
Thread.Sleep(0)可以將CPU讓給同優先級或更高優先級的線程,而Thread.Yield可以將CPU讓給更低優先級的,介於Thread.Sleep(0)和Thread.Sleep(1)之間。