C#的变迁史01 - C# 1.0篇
? ? ? C#與.NET平臺誕生已有10數年了,在每次重大的版本升級中,微軟都為這門年輕的語言添加了許多實用的特性,下面我們就來看看每個版本都有些什么。老實說,分清這些并沒什么太大的實際意義,但是很多老資格的.NET程序員還是熱衷在面試中咬文嚼字,所以茶余飯后了解一下也是不錯的,所說的內容是對還是錯,大家還是勤搜搜看,有誤導的地方的還請見諒。
? ? ? 第一代C#只是具有了面向對象語言該有的大框架。這些元素,基本上是每種現代化面向對象語言都有的。
1. 面向對象的基本特征 - 繼承,封裝,多態
? ? ? 我相信任何一門現代化的面向對象的語言必然實現這些機制,這些東東必然是不可缺少的,這是面向對象編程的核心:繼承機制復用了代碼,多態機制表征了變化,封裝機制隱藏了細節。
? ? ? 也許JavaScript的同仁們不太同意這個說法,確實是,以函數為第一類成員的JS確實淡化了類,沒有了接口的概念,但是以Object作為單一根,原型鏈實現的對象的繼承,還是隱約可以看到面向對象的特質。
? ? ? 在C#的實現中,本質上是函數,使用時卻像是字段一樣的Property比較特別,個人覺得這種設計使用上相當的方便。當然了,有些莫名其妙的面試者總是問C#中Attribute與Property有什么不同?我實在想問一下兄弟們:難道就因為這兩個概念翻譯過來都可以叫做"屬性",就能生搬硬套搞出個辨析題?
2. 基本數據類型,值類型與引用類型
? ? ? 對于常用的的操作數,C#也提供了基本的數據類型:int, double, bool, byte...。這些類型由于使用的太多,而且特性簡單,創建和銷毀比較容易,于是被設計成值類型(值拷貝,不允許繼承)。除了這幾種類型,其他的所有類型,包括自定義的類,接口,集合等出于傳遞性能等因素都被設計成引用類型(地址拷貝)。
? ? ? 把一個類型設計成值類型,還是引用類型,這是一個問題。
? ? ? C++把所有類型設計成了值類型,于是不得不引入引用的概念,來優化內存和傳參時的效率問題。據稱,Java把所有的類型設計成引用類型,也是帶來顯著的性能問題。到了C#的時候,分類處理了一下,既有使用最密集的值類型,也有擴展性最好的引用類型。
? ? ? 值類型是在定義的地方分配的,要么是線程堆棧上(函數參數),要么是托管堆上(作為引用類型的成員),引用類型只會在托管堆上分配。值類型傳遞的是拷貝,引用類型傳遞地址,這在傳參時最為明顯,傳進來的值類型對象修改其值后并不會影響傳經來之前的值,而引用類型傳進來后,修改其值就是修改傳進來之前的值,這個特點導致了很多隱藏的問題。當然值類型也可以傳遞地址給被調用的函數,這樣修改傳進來的值就是修改原來的值,這就是參數列表中ref與out關鍵字的作用,這個關鍵字顯然對引用類型沒什么效果。
? ? ? 分類處理想法很好,卻帶來了的一定的復雜行,作為.NET單一根的Object類是一個引用類型,而子類卻有一部分是值類型,于是為了滿足面向對象"使用基類的地方也可以使用子類"的約定,需要在需要Object類型的時候把值類型包裝一下,添加上該有的一些指針,變成引用類型傳出去,這就是裝箱,在真正使用的時候再把值拷貝出來轉成值類型,這是拆箱,在沒有泛型之前,這種效率損失有時真的難以讓人忍受,特別是某些效率至上的同仁。
? ? ? 說到效率,就不得不說說特殊的string類型,這種類型的使用范圍很廣,C#的根類Object都提供了ToString方法。從用戶角度來說,任何的輸出和輸入都是字符串,這是字符串受到重視的原因。但是字符串的特性、實現與運算比較復雜,被設計成引用類型。string類的使用方式也是嚴重影響效率的一個方面。string類實現了字符串的恒定性,而且使用了駐留機制,任何字符串修改操作都是生成新的字符串,這使得如果高頻率的使用字符串的修改方法(例如大規模的循環)時,內存會表示壓力很大。StringBuilder恰到好處的解決了這些問題,因而成為了string類使用者的貼心伴侶。
? ? ? 此外,深拷貝與淺拷貝也是很常見的一個問題。深拷貝是把對象的所有數據全部拷貝一份,包括對象的成員指向的其他的對象也如此拷貝。而淺拷貝只是把當前對象的所有成員拷貝一份,如果該對象有成員指向別的對象,就不管了。
? ? ? 毫無疑問,所有值類型實現的是深拷貝,因為它們沒有指針指向別的地方,拷貝的時候就是把自己復制一份出來。
? ? ? 引用類型就復雜了,因為引用類型內部的成員可能是引用類型,它會指向了別的內存空間。如果實現淺拷貝,那么引用類型的復制品中的引用成員還是與源對象一樣,會指向同一個內存空間。如果實現深拷貝,那么被引用的內存空間也會復制一份,拷貝得到的新對象的成員會指向這個新的內存空間。
? ? ? C#中ICloneable接口提供了拷貝接口,需要實現這個功能的類需要自己實現淺拷貝或者深拷貝。此外,C#中基類Object中提供了淺拷貝的實現,所以自己的類中實現淺拷貝的方法最簡單的就是調用Object的MemberwiseClone方法。注意C#中不指定基類的類默認都是從Object繼承的。實現深拷貝就需要自己去復制和創建新的成員對象了,當然借助序列化和反序列化也是一種實現深拷貝的方式,因為反序列化的時候會創建全新的對象和引用關系。
3. 集合
? ? ? 語言是解決實際問題的,實際中有群體的概念,程序中自然就有集合的概念,這是類型系統中不可或缺的重量級成員。集合是第一版C#中裝箱拆箱的重災區,使用集合的時候到處充斥著難以忍受的類型裝換以及性能損失。
? ? ? C#的常用集合如Array,ArrayList,Queue,Stack等等都是基于連續內存的一種實現,簡單的說都是基于數組實現的,這個自然是訪問性與插入刪除這兩種類型操作之間效率權衡的結果。當然了C#中也有像LinkedList這種鏈表類集合,實際使用中需要根據操作類型(讀取為主?修改為主?)的頻率選擇合適的集合。
? ? ? 與集合密切相關的是迭代器模式,集合負責保存元素,遍歷和枚舉的過程就交給了迭代器,這是符合面向對象設計中單一職責原則的。在C#中,如果想使用foreach這種迭代語法的時候,需要先檢查一下你自定義的集合是否實現了IEnumerable接口,當然了,內置的集合全部是實現了該接口的。
4. 消息機制,事件與代理
? ? ? 對象有了,那么下一步就是協同工作了。使用delegate定義回調函數的樣式與使用event定義消息通知的接口構成了C#通信的基本基調:注冊,通知,回調;這也是觀察者模式的核心內容。
? ? ? 作為安全的函數指針,delegate近乎完美的完成了自己的工作,但是使用起來需要采用new實例的方式去初始化,似乎不夠方便,后面的版本中微軟提供了更方便的實現方式。
5. 多線程(Thread方式)
? ? ? 多線程與異步運行這是必須的,那么多的事情總不能一步接一步去做吧,有些事是沒有嚴格的先后次序的,而且時間就是金錢啊,能省就省唄。
? ? ? 內存的運行速度是杠杠的,那些外設的速度完全趕不上,但是基本上所有的程序都是要與外設打交道的,為了不浪費內存的時間,異步勢在必行。原本來說,異步指的就是內存與外設不同步這個意思。
? ? ? 同步執行大家都知道是程序一句接一句的執行,由于上面所說的異步是勢在必行的,所以當編寫程序的時候,為了不浪費內存的時間,有時候就需要異步的執行一些方法,這在C#中是允許的。
? ? ? 在.NET類庫中有很多異步調用的方法。一般都是已Begin開頭End結尾構成一對異步委托方法,外加兩個回調函數和AsyncState參數,組成異步操作的結構。異步編程,就是借助delegate,BeginInvoke,EndInvoke,AsyncCallBack,AsyncState,IAsycResult等類或方式來完成異步操作。
? ? ? 多線程其實與異步沒一毛錢關系,但是很多人習慣一起說,而且它們執行的方式有那么一點相同,那咱也一起說吧。多線程現在更多的是與多核聯系在一起(單核的時候分時間片去運行不同的線程),既然咱不差錢,多買了幾個核,不用總是浪費的,浪費可恥啊,那就用上,于是沒有先后依賴關系的相對獨立的一些任務就可以放到別的線程中做了,做好后以一定的方式(回調或者直接把結果塞回來)通知一下主線程就可以了。
? ? ? 以Thread類為核心的一組類提供了基本的多線程功能,它們功能多樣,用戶可控性高(可以隨時申請中止線程等操作),是許多多線程用戶的最愛。同時,C#還有一個叫ThreadPool的玩意兒,用戶只要塞給它一些活就不用管了,喝杯咖啡等待結果即可,十分方便,不喜歡操控性的用戶很喜歡這種感覺。
?
? ? ? 當然除了這些通用的元素,C#自身也存在很多閃光點:
1. Attribute特性
? ? ? 這是C#特有的,其他語言還真沒有這個東東。元數據是C#程序的重要信息,這些數據攜帶了一些信息,根據這些信息,負責解析的類或者框架可以完成一些很特別的功能。比如enum類型,當它帶上Flag這頂帽子的時候,乖乖不得了了,它居然可以參與位運算了。一定程度上說,Attribute是一套強大的系統,一套強大的自定義系統。
2. 自動垃圾回收
? ? ? 自動垃圾回收是通過GC這個類完成的,一般沒什么人會去調用這個類的方法強制回收。自動垃圾回收以前可以要掛起相關線程,回收程序所有"根"沒有引用的垃圾,壓縮托管堆,重新修改引用的地址,然后再恢復現場的。效率應該不高,但是本人感受不深。據說現在可以回收的時候不掛起所有線程,不知道怎么樣。
? ? ? 垃圾回收與代齡的問題一般只是停留在討論的層次,很少有人實際寫程序中使用。但是與之相關的Dispose模式就風光了,如何實現一個標準的Dispose模式據說是很多Senior考生的必考項目。
? ? ? Dispose模式與using息息相關,using關鍵字后面的資源一定要實現IDisposible接口,這是using引用namespace之外的另一處用法,如何實現這個模式在網上一搜一大把,兄弟們自己看著辦吧。
? ? ? 此外,與垃圾回收息息相關的還有"~"方法,這個在C++中稱為析構函數的家伙在C#一躍稱為Finalize方法,標準的Dispose模式是包含這個函數處理的。實現了這個方法的類是需要二次回收的,第一次回收的時候這些對象托管資源釋放以后,需要進行第二次回收,通常是回收非托管資源。處于兩次回收之間的這些對象是可以復活的,但是實際中好像做的人不是太多。
3. 反射與動態創建
? ? ? 動態創建時很酷的一個特性,很多語言比如Java中也有。通常是提供一個配置文件,XML可以,普通的TXT也可以,從里面讀出一個字符串,然后從這個字符串變出一個類的實例然后使用,多炫啊。當然了反射不僅僅可以干這個,它還可以動態的加載一個dll,動態的創建類型,動態的調用類型的方法,這些都是字符串驅動的(參數都是字符串),是不是很帥?System.Activator,System.Reflection.Assembly,System.Type等是完成了這一特性的得力干將。
4. 中間語言的概念與CLR
? ? ? 這兩個家伙與自動垃圾回收是托管語言(微軟提出來的概念,所以托管語言指的就是基于.NET的幾種開發語言)的基石,他們徹底隔離了機器碼與編程語言,于是C#,VB.NET,托管C++等編譯后萬祖歸宗,編譯后都變成了中間代碼,等到第一次運行的時候,JIT編譯器會將這些中間代碼轉換成真正的機器代碼,這樣據說很好的提供了程序的移植性。
? ? ? 當然了為了提高程序啟動的性能(總不能每次運行程序都需要JIT編譯一下吧!),首次運行時JIT編譯好的機器碼會被秘密的保存在一個地方,這樣下次運行程序的時候,就不需要JIT再次編譯一下了。此外,很多高級玩家宣稱:不懂中間語言IL,就不算真懂C#。對此,本人持保留態度,因為從我的角度來說,使用好語言才是目前的首要工作,刨根問底是以后的事,呵呵。
5. 統一而且完備的類庫FCL
? ? ? ?提供一個完整的類庫是相當有必要的,和以前的MFC是一個道理。使用一門語言時,比如JavaScript,每次當你看到多如牛毛的第三方插件和類庫的時候,難道你沒有想吐的欲望?這是微軟相當具有競爭力的一個做法,不管你信不信,反正我信了。完備的FCL是支撐.NET大廈的磚頭,沒有這些東西,就沒有.NET的各項功能,也就沒有各位大佬們面試時拍人的武器了。
總結
以上是生活随笔為你收集整理的C#的变迁史01 - C# 1.0篇的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 苹果被告恶意诱导:1人胜诉 2500万人
- 下一篇: 热热热!全国506个高温预警生效中 河
