C#的变迁史 - C# 2.0篇
在此重申一下,本文僅代表個(gè)人觀點(diǎn),如有不妥之處,還請(qǐng)自己辨別。
第一代的值類型裝箱與拆箱的效率極其低下,特別是在集合中的表現(xiàn),所以第二代C#重點(diǎn)解決了裝箱的問題,加入了泛型。
1. 泛型 - 珍惜生命,遠(yuǎn)離裝箱
集合作為通用的容器,為了兼容各種類型,不得已使用根類Object作為成員類型,這在C#1.0中帶來了很大的裝箱拆箱問題。為了C#光明的前途,這個(gè)問題必須要解決,而且要解決好。
C++模板是一個(gè)有用的啟迪,雖然C++模板的運(yùn)行機(jī)制不一樣,但是思路確實(shí)是正確的。
帶有形參的類型,也就是C#中的泛型,作為一種方案,解決了裝箱拆箱,類型安全,重用集合的功能,防止具有相似功能的類泛濫等問題。泛型最大的戰(zhàn)場就是在集合中,以List<T>,Queue<T>,Stack<T>等泛型版本的集合基本取代了第一代中非泛型版本集合的使用場合。當(dāng)然除了在集合中,泛型在其他的地方也有廣泛的用途,因?yàn)槌绦騿T都是懶的,重用和應(yīng)對(duì)變化是計(jì)算機(jī)編程技術(shù)向前發(fā)展最根本的動(dòng)力。
為了達(dá)到類型安全(比如調(diào)用的方法要存在),就必須有約定。本著早發(fā)現(xiàn),早解決的思路,在編譯階段能發(fā)現(xiàn)的問題最好還是在編譯階段就發(fā)現(xiàn),所以泛型就有了約束條件。泛型的約束常見的是下面幾種:
a. 構(gòu)造函數(shù)約束(使用new關(guān)鍵字),這個(gè)約束要求實(shí)例化泛型參數(shù)的時(shí)候要求傳入的類必須有公開的無參構(gòu)造函數(shù)。
b. 值類型約束(使用struct關(guān)鍵字),這個(gè)約束要求實(shí)例化泛型參數(shù)的類型必須是值類型。
c. 引用類型約束(使用class關(guān)鍵字),這個(gè)約束要求實(shí)例化泛型參數(shù)的類型必須是引用類型。
d. 繼承關(guān)系約束(使用具體制定的類或接口),這個(gè)約束要求實(shí)例化泛型參數(shù)的類型必須是指定類型或是其子類。
當(dāng)然了,泛型參數(shù)的約束是可以同時(shí)存在多個(gè)的,參看下面的例子:
public class Employee {} class MyList<T, V> where T: Employee, IComparable<T>where V: new() { }如果不指定約束條件,那么默認(rèn)的約束條件是Object,這個(gè)就不多講了。
當(dāng)使用泛型方法的時(shí)候,需要注意,在同一個(gè)對(duì)象中,泛型版本與非泛型版本的方法如果編譯時(shí)能明確關(guān)聯(lián)到不同的定義是構(gòu)成重載的。例如:
public void Function1<T>(T a); public void Function1<U>(U a); 這樣是不能構(gòu)成泛型方法的重載。因?yàn)榫幾g器無法確定泛型類型T和U是否不同,也就無法確定這兩個(gè)方法是否不同public void Function1<T>(int x); public void Function1(int x); 這樣可以構(gòu)成重載public void Function1<T>(T t) where T:A; public void Function1<T>(T t) where T:B; 這樣不能構(gòu)成泛型方法的重載。因?yàn)榫幾g器無法確定約束條件中的A和B是否不同,也就無法確定這兩個(gè)方法是否不同使用泛型就簡單了,直接把類型塞給形參,然后當(dāng)普通的類型使用就可以了。例如:
List<int> ages = new List<int>(); ages.Add(0); ages.Add(1); ages.Remove(1);2. 匿名函數(shù)delegate
在C# 2.0中,終于實(shí)例化一個(gè)delegate不再需要使用通用的new方式了。使用delegate關(guān)鍵字就可以直接去實(shí)例化一個(gè)delegate。這種沒有名字的函數(shù)就是匿名函數(shù)。這個(gè)不知道是不是語法糖的玩意兒使用起來確實(shí)比先定義一個(gè)函數(shù),然后new實(shí)例的方式要方便。不過最方便的使用方式將在下一版中將會(huì)到來。
delegate void TestDelegate(string s); static void M(string s) {Console.WriteLine(s); }//C# 1.0的方式 TestDelegate testDelA = new TestDelegate(M);//C# 2.0 匿名方法 TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };談到匿名函數(shù),不得不說說閉包的概念。
如果把函數(shù)的工作范圍比作一個(gè)監(jiān)獄的話,函數(shù)內(nèi)定義的變量就都是監(jiān)獄中的囚犯,它們只能在這個(gè)范圍內(nèi)工作。一旦方法調(diào)用結(jié)束了,CLR就要回收線程堆棧空間,恢復(fù)函數(shù)調(diào)用前的現(xiàn)場;這些在函數(shù)中定義的變量就全部被銷毀或者待銷毀。但是有一種情況是不一樣的,那就是某個(gè)變量的工作范圍被人為的延長了,通俗的講就像是某囚犯越獄了,它的工作范圍超過了劃定的監(jiān)獄范圍,這個(gè)時(shí)候它的生命周期就延長了。
閉包就是使用函數(shù)作為手段延長外層函數(shù)中定義的變量的作用域和生命周期的現(xiàn)象,作為手段的這個(gè)函數(shù)就是閉包函數(shù)。看一個(gè)例子:
class Program{static void Main(string[] args){List<Action> actions = getActions();foreach (var item in actions){item.Invoke();}}static List<Action> getActions(){List<Action> actions = new List<Action>();for (int i = 0; i < 5; i++){Action item = delegate() { Console.WriteLine(i); };actions.Add(item);}return actions;}}你可以試試運(yùn)行這個(gè)例子,結(jié)果和你預(yù)想的一致嗎?這個(gè)例子會(huì)輸出5個(gè)5,而不是0到4,出現(xiàn)這個(gè)現(xiàn)象的原因就是閉包。getActions函數(shù)中的變量i被匿名函數(shù)引用了,它在getActions調(diào)用結(jié)束后還會(huì)一直存活到匿名函數(shù)執(zhí)行結(jié)束。但是匿名函數(shù)是后面才調(diào)用的,執(zhí)行它們的時(shí)候,i早就循環(huán)完畢,值是5,所以最終所有的匿名函數(shù)執(zhí)行結(jié)果都是輸出5,這是由閉包現(xiàn)象導(dǎo)致的一個(gè)bug。
要想修復(fù)這個(gè)由閉包導(dǎo)致的問題,方法基本上是破壞閉包引用,方式多種多樣,下面是簡單的利用值類型的深拷貝實(shí)現(xiàn)目的。
第一個(gè)方法:讓閉包引用不再指向同一個(gè)變量
for (int i = 0; i < 5; i++) {int j = i;Action item = delegate() { Console.WriteLine(j); };actions.Add(item); }第二個(gè)方法:包上一層函數(shù)來構(gòu)造新的作用域
static List<Action> getActions() {List<Action> actions = new List<Action>();for (int i = 0; i < 5; i++){Action item = ActionMethod(i);actions.Add(item);}return actions; }static Action ActionMethod(int p) {return delegate(){Console.WriteLine(p);}; }閉包現(xiàn)象提醒我們使用匿名函數(shù)和3.0中的Lambda表達(dá)式時(shí)都要時(shí)刻注意變量的來源。
3. 迭代器
在C# 1.0中,集合實(shí)現(xiàn)迭代器模式是需要實(shí)現(xiàn)IEnumerable的,這個(gè)大家還記得吧,這個(gè)接口的核心就是GetEnumerator方法。實(shí)現(xiàn)這個(gè)接口主要是為了得到Enumerator對(duì)象,然后通過其提供的方法遍歷集合(主要是Current屬性和MoveNext方法)。自己去實(shí)現(xiàn)這些還是比較麻煩的,先需要定義一個(gè)Enumerator對(duì)象,然后在自定義的集合對(duì)象中還需要實(shí)現(xiàn)IEnumerable接口返回定義的Enumerator對(duì)象,于是一個(gè)新的語法糖就出現(xiàn)了: yield關(guān)鍵字。
在C# 2.0中,只需要在自定義的集合對(duì)象中還需要實(shí)現(xiàn)IEnumerable接口返回一個(gè)Enumerator對(duì)象就行了,這個(gè)創(chuàng)建Enumerator對(duì)象的工作就由編譯器自己完成了。看一個(gè)簡單的小例子:
public class Stack<T>:IEnumerable<T> {T[] items;int count;public void Push(T data){...}public T Pop(){...}public IEnumerator<T> GetEnumerator(){for(int i=count-1;i>=0;--i){yield return items[i];}} }使用yield return創(chuàng)建一個(gè)Enumerator對(duì)象是不是很方便?編譯器遇到y(tǒng)ield return會(huì)創(chuàng)建一個(gè)Enumerator對(duì)象并自動(dòng)維護(hù)這個(gè)對(duì)象。
當(dāng)然了,多數(shù)時(shí)候foreach必要遍歷集合中的每一個(gè)元素,這個(gè)時(shí)候使用yield return配合for循環(huán)枚舉每個(gè)元素就可以了,但是有時(shí)候只需要返回滿足條件的部分元素,這個(gè)時(shí)候就要結(jié)合yield break中斷枚舉了,看一下:
//使用yield break中斷迭代: for(int i=count-1;i>=0;--i) {yield return items[i];if(items[i]>10){yield break;} }4. 可空類型
這個(gè)特性我覺得又是把值類型設(shè)計(jì)成引用類型Object類子類后,微軟生產(chǎn)的怪語法。空值是引用類型的默認(rèn)值,0值是值類型的默認(rèn)值。那么在某些場合,比如從數(shù)據(jù)庫中的記錄取到內(nèi)存中以后,沒有值代表的是空值,但是字段的類型卻是值類型,怎么搞呢?于是整出了可空類型。當(dāng)然了,這個(gè)問題可以通過在設(shè)計(jì)表的時(shí)候給字段設(shè)計(jì)一個(gè)默認(rèn)值來解決,但是有的時(shí)候某些字段的設(shè)置默認(rèn)值是沒有意義的,比如年齡,0有意義嗎?
可空類型的概念很簡單,沒什么可說的,不過一個(gè)相似的語法卻讓我感到很舒服:那就是"??"操作符。這是一個(gè)二元操作符,如果第一個(gè)操作數(shù)是空值,則執(zhí)行第二個(gè)操作數(shù)代表的操作,并返回其結(jié)果。例如:
static void Main(string[] args) {int? age = null;age = age ?? 10;Console.WriteLine(age); }5. 部分類與部分方法
這一特性還是比較好用的,終于不用把所有的內(nèi)容擠到一起了,終于可以申明和實(shí)現(xiàn)相分離了,雖然好像以前也可以做到,但是現(xiàn)在這項(xiàng)權(quán)利也下放給人民群眾了。partial關(guān)鍵字帶來了這一切,也帶來了一定的擴(kuò)展性。這個(gè)特性也比較簡單,就是使用partial關(guān)鍵字。編譯的時(shí)候,這些文件中定義的部分類會(huì)被合并。部分方法是3.0的特性,不過沒什么新意,就放到2.0一起說吧。
使用這個(gè)特性的時(shí)候需要注意:
a. 部分方法只能在部分類中定義。
b. 部分類和部分方法的簽名應(yīng)該是一致的。
c. partial用在類型上的時(shí)候只能出現(xiàn)在緊靠關(guān)鍵字 class、struct 或 interface 前面的位置。
d. public等訪問修飾符必須出現(xiàn)在partial前面。
f. partial定義的東西必須是在同一程序集和模塊中。
看一個(gè)簡單的例子:
// File1.cs namespace PC {partial class A{int num = 0;void MethodA() { }partial void MethodC();} }// File2.cs namespace PC {partial class A{void MethodB() { }partial void MethodC() { }} }這里需要注意一下MethodC的申明和定義是分開的就可以了。
還有一點(diǎn),很多人認(rèn)為partial破壞了類的封裝性,實(shí)際上談不上。因?yàn)橐粋€(gè)類能分部,就說明類的設(shè)計(jì)者認(rèn)為是需要保留這個(gè)擴(kuò)展性的,所以后面的人才可以給這個(gè)類添加一些新的東西。
6. 靜態(tài)類
這個(gè)特性也是比較符合實(shí)際情況的,很多情況下,某些對(duì)象只需要實(shí)例化一次,然后到處使用,單件模式是可以實(shí)現(xiàn)這個(gè)目的,現(xiàn)在靜態(tài)類也是一個(gè)新的選擇。
靜態(tài)類只能含有靜態(tài)成員,所以構(gòu)造函數(shù)也是靜態(tài)的,既然是靜態(tài)的,那么它與繼承就沒什么關(guān)系了。靜態(tài)類從首次調(diào)用的時(shí)候創(chuàng)建,一直到程序結(jié)束時(shí)銷毀。
簡單看一個(gè)小例子:
public static class A {static string message = "Message";static A(){Console.WriteLine("Initialize!");}public static void M(){Console.WriteLine(message);} }不過據(jù)經(jīng)驗(yàn)講,有沒有靜態(tài)構(gòu)造函數(shù)對(duì)靜態(tài)類的構(gòu)造時(shí)間是有影響的,一個(gè)是出現(xiàn)在首次使用對(duì)象成員的時(shí)候,一個(gè)是程序集加載的時(shí)候,不過分清這個(gè)實(shí)在沒什么意義,有興趣的同學(xué)自己研究吧。
C#2.0的新特性絕不止這幾個(gè),但是對(duì)程序猿們影響比較大的都在這了,更多的就參看微軟的MSDN吧。
轉(zhuǎn)載于:https://www.cnblogs.com/dxy1982/p/3598160.html
總結(jié)
以上是生活随笔為你收集整理的C#的变迁史 - C# 2.0篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LZMA demo挑选使用备忘
- 下一篇: 几种支持REST的Java框架