【读书笔记】《框架设计(第2版)CLR Via C#》中两个比较有趣的知识点
本季度公司要求閱讀《框架設計(第2版)CLR Via C#》這本書,由于前兩個月一直很忙,也沒有時間閱讀,偶爾閱讀也是很晚回家以后臨睡前拿起這經典之作讀那么一個小節。最近利用周末可以說一鼓作氣的看了大半本,感觸很深。之前,這本書我閱讀過第一版,那時好像叫《.NET框架設計》,當時就特別欽佩JR大叔的技術功底和寫作技巧,正向書中有人評論的那樣,JR確實適合于把枯澀難懂的概念用簡短的語言描述清楚。還有下個季度需要閱讀的《WINDOWS核心編程》更是經典中的經典(幸會這本書我閱讀過),經典也是毫無疑問的,我這里只是感慨一下,相信閱讀過這兩本書的人都有這種感慨了。這兩本書我覺得是一個WINDOWS程序員應該買來閱讀并且擺在案頭收藏的。
在閱讀這本書時我發現很多值得思考和有趣的地方,例如:JR關于調用的參數和返回值的建議;位索引器示例;觸發事件的事件安全;字符串格式化和字符串的駐留等。尤其是.NET的垃圾回收機制在這本書中講的很詳細。其中有兩個知識點是讓我感到收獲很大的地方而且例子也很詳細,我在這里就單獨拿出來與大家分享,同時也作為知識點進行總結,這里講沒什么技術含量,大家別BS我。
第一部分:常量,只讀字段,靜態字段,靜態只讀字段區別與比較
常量:常量就是指永遠不會改變的符號,在.Net通過編譯以后常量的值會插入到程序集的元數據中,所以常量的類型必須是.Net的基元類型Boolean,Char,Byte,SByte,Int16,Int32,UInt32,Int64,UInt64,Singe,Double,Decimal和String。也就是說在一個程序集A的類中定義一個常量,在另一個程序集B中使用這個常量,當兩個程序集編譯以后,C#編譯器會將這個常量插入到使用這個常量的程序集B的元數據中,而這時使用常量的程序集A對定義常量的程序集B沒有運行時的依賴關系了,也就是說在運行時可以刪除定義常量的程序集A,同時也說明如果修改這個常量的值再去編譯定義常量的程序集A不會改變使用常量的程序集B在運行時獲取到的這個常量的值,常量在帶來這種好處的同時顯然對于程序集的版本控制是很不利的。如果一個程序集需要從另一個程序集中總是獲得最新的數據,則不能使用常量,這時可以使用只讀字段。此外,常量具有static的含義,只能通過類型訪問,不能通過對象訪問。
只讀字段:只讀字段也是在初始化(這個初始化是在運行時初初始化的)以后在驗證過程中不允許修改的字段,只讀字段引用的可以是任何類型的對象。但要注意只讀字段只有在對象初始化時可以給這個字段賦值,也就是字段在初始化時還具有可寫屬性,以后這個字段就只讀了,字段的只讀含義是這個變量的引用不可以改變了,但是具體引用的對象的狀態時可以改變的。編譯器和驗證機制確保只讀字段不會被任何其他方法寫入,但是我們依然可以使用反射修改只讀字段。只讀字段屬于對象實例的,需要通過對象訪問,屬于對象狀態的一部分。
靜態字段:靜態字段是與類型關聯的成員(只能通過類型訪問,不像Java還可以通過類的實例訪問,如果沒記錯的話),靜態字段的初始化是在類型被加載到CLR中時執行的。靜態字段可以應用任何類型的對象。
靜態只讀字段:靜態只讀字段與常量比較類似,但是靜態只讀字段不限于基元類型,可以是.Net中的任何類型,靜態只讀字段不像常量是在編譯時插入到元數據中的,而靜態只讀字段是在運行時賦值和確定的,也就是對于程序的依賴是一種強依賴關系,具體的值是在運行時去程序集中取到的,而不像常量做一個拷貝插入元數據中。靜態只讀字段屬于類型的,通過類型訪問。
通過反射修改私有字段,下面一個示例通過反射修改私有的一般字段,常量,只讀字段,靜態字段,靜態只讀字段并打印出結果。在這里可以可以看出反射是很強大的,既然可以修改私有的只讀字段,但要注意實際上通過反射也無法修改常量,修改會報錯,這與常量的存儲方式有關。通過反射給人一種可以跳過驗證規則的假象。示例代碼:
通過反射修改私有字段 using?System;using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?System.Reflection;
namespace?FalseVerification
{
????class?Program
????{
????????static?void?Main(string[]?args)
????????{
????????????Type?type?=?typeof(SomeType);
????????????SomeType?st?=?new?SomeType();
????????????st.Print();
????????????//?正常字段,當然可以修改
????????????FieldInfo?fi?=?type.GetField("f1",?BindingFlags.NonPublic?|?BindingFlags.Instance);
????????????fi.SetValue(st,?(Int32)fi.GetValue(st)?+?1);
????????????//?常量字段,反射也無法修改,如果取消下面語句的注釋,執行會出錯
????????????/*?原因說明:常量的值必須在編譯時就確定(只能是基元類型),也就是說在定義時就賦值。
???????????????編譯后常量的值是保存在程序集的元數據中,在運行時是不可修改的;
???????????????而其它字段是存儲在動態內存中,在運行時是可修改的。*/
????????????fi?=?type.GetField("f2",?BindingFlags.NonPublic?|?BindingFlags.Static?|?BindingFlags.Instance);
????????????//fi.SetValue(null,?(Int32)fi.GetValue(null)?+?1);
????????????//?只讀字段,可通過反射方式修改值
????????????fi?=?type.GetField("f3",?BindingFlags.NonPublic?|?BindingFlags.Instance);
????????????fi.SetValue(st,?(Int32)fi.GetValue(st)?+?1);
????????????//?靜態字段,也可修改
????????????fi?=?type.GetField("f4",?BindingFlags.NonPublic?|?BindingFlags.Static);
????????????fi.SetValue(null,?(Int32)fi.GetValue(null)?+?1);
????????????//?靜態只讀字段,下面代碼不出錯,改變了反射字段的值,但類中的字段值并沒有被改變
????????????fi?=?type.GetField("f5",?BindingFlags.NonPublic?|?BindingFlags.Static);
????????????fi.SetValue(null,?(Int32)fi.GetValue(null)?+?1);
????????????Int32?f5?=?(Int32)fi.GetValue(null);?//?i5?得到值為51
????????????st.Print();
????????????Console.WriteLine("f5:?"?+?f5.ToString());
????????????Console.ReadKey();
????????}
????}
????public?class?SomeType
????{
????????private?Int32?f1?=?30;//?私有字段
????????private?const?Int32?f2?=?10;//?私有常量字段
????????private?readonly?Int32?f3?=?20;//?私有只讀字段
????????private?static?Int32?f4?=?40;//?私有靜態字段
????????private?static?readonly?Int32?f5?=?50;//?私有靜態只讀字段
????????public?void?Print()
????????{
????????????Console.WriteLine("f1:?"?+?f1.ToString());
????????????Console.WriteLine("f2:?"?+?f2.ToString());
????????????Console.WriteLine("f3:?"?+?f3.ToString());
????????????Console.WriteLine("f4:?"?+?SomeType.f4.ToString());
????????????Console.WriteLine("f5:?"?+?SomeType.f5.ToString());
????????????Console.WriteLine();
????????}
????}
}
?運行結果:
從這個結果可以看出,通過反射可以修改私有的普通字段,可以修改只讀字段,可以修改靜態字段,修改這三個都沒有問題,但是不可以修改常量字段。當通過反射修改靜態只讀字段時(有點類似常量)時,可以正常執行,但是通過類獲取的靜態只讀字段并沒有改變,說明實際并沒有修改成功,我們實際修改的只是通過反射獲取的靜態只讀字段,并沒有實際映射回去。具體原因感興趣的同學可以通過IL分析一下,鄙人不才,不會IL。
第二部分:GC中的GCBeep示例
先看看代碼:
GCBeep using?System;using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
namespace?GCBeep
{
????class?Program
????{
????????static?void?Main(string[]?args)
????????{
????????????new?GCBeep();
????????????//?創建很多對象,讓GC執行垃圾收集
????????????for?(Int32?x?=?0;?x?<?10000;?x++)
????????????{
????????????????Console.WriteLine(x);
????????????????Byte[]?b?=?new?Byte[100];
????????????}
????????}
????}
????internal?sealed?class?GCBeep
????{
????????/*?
?????????這是一個Finalize方法,GC在發現根
?????????中沒有對于GCBeep對象的引用時會在
?????????垃圾收集時調用這個方法
????????*/
????????~GCBeep()
????????{
????????????Console.Beep();//?讓控制臺發出報警的聲音
????????????if?(!AppDomain.CurrentDomain.IsFinalizingForUnload()?&&?!Environment.HasShutdownStarted)
????????????{
????????????????//?如果不是在應用程序域卸載或應用程序自行關閉時,新建一個GCBeep對象并沒有變量引用它
????????????????new?GCBeep();
????????????}
????????}
????}
}
GC在垃圾收集時會執行對象的Finalize方法來釋放非托管的資源,在這里也就是~GCBeep(),在~GCBeep()方法內部首先讓控制臺發出報警聲,之后會在不是應用程序域卸載和應用程序主動退出的情況下再創建一個GCBeep對象,并不讓任何變量引用它,此時這個對象實際上已經成為下次GC執行垃圾收集的目標。在主函數中同樣也是創建一個無變量引用的GCBeep對象作為程序的入口點,之后創建大量的對象,當托管堆的相關內存用完之后垃圾收集就會執行來回收垃圾釋放內存以便容納新創建的對象,這時垃圾收集調用~GCBeep()發出報警聲。創建10000個Byte[] b = new Byte[100];在我的x86的win7筆記本上發出兩次報警聲,大家可以修改10000到更大測試,來根據報警聲來判斷執行~GCBeep()的次數。
這個例子我覺得設計的很精美,也很簡潔,更重要的是可以說明垃圾收集時確實自動執行了對象的Finalize方法來釋放非托管資源(作為示例這里并沒有實際釋放非托管資源)。
?
轉載于:https://www.cnblogs.com/Thriving-Country/archive/2009/12/20/1628314.html
總結
以上是生活随笔為你收集整理的【读书笔记】《框架设计(第2版)CLR Via C#》中两个比较有趣的知识点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决“计划任务不存在的问题”方法
- 下一篇: javascript indexOf函数