前言html
C#1.0的委託特性使方法做爲其餘方法的參數來傳遞,而C#2.0 中提出的泛型特性則使類型能夠被參數化,從而沒必要再爲不一樣的類型提供特殊版本的實現方法。
另外C#2.0還提出了可空類型,匿名方法和迭代器3個優美的特性。數組
1,泛型
1.1 泛型是什麼
泛型的英文表述是"generic", 這個單詞意爲通用的。從字面意思可知,泛型表明的就是"通用類型",它能夠代替任意的數據類型,使類型參數化,
從而達到之實現一個方法就能夠操做多種數據類型的目的。泛型是將方法實現行爲與方法操做的數據類型分離,實現了代碼重用。安全
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //用int做爲實際參數來促使花泛型類型 6 List<int> intList = new List<int>(); 7 //從int列表添加元素3 8 intList.Add(3); 9 10 //用string做爲實際參數來初始化泛型類型11 List<string> stringList = new List<string>();12 //從string列表添加元素13 stringList.Add("wanmg-meng");14 }15 }
在以上的代碼中,List<T> 是.Net 類庫中實現的泛型類型,T是泛型參數(可理解爲形參), 若是想實例化一個泛型類型,必須傳入實際的參數類型。微信
泛型除了能夠實現代碼重用外, 還提供了更好的性能和類型安全特性. 前面關於拆箱裝箱講過. 應用類型和值類型間存在着相互轉換,轉換的過程稱爲裝箱和拆箱. 這對過程會引發必定的性能損失. 而泛型是避免性能損失的有效方法.ide
1.2全面解析泛型
在前面的泛型代碼中, T就是類型參數. 不管調用類型方法仍是初始化泛型實例, 都須要用真實類型來替換T. 能夠將T理解爲類型的一個佔位符, 即告訴編譯器, 在調用泛型時必須爲其指定一個實際類型.
1.2.1
已構造泛型又可分爲開放類型和密封類型. 其中, 開放類型是指包含類型參數的泛型,全部未綁定的泛型類型都屬於開放類型; 而封閉類型則是指那些已經爲每個類型參數都傳遞了司機數據類型的泛型.函數
1 //聲明開放泛型類型 2 public class DictionaryStringKey<T> : Dictionary<string, T> 3 { 4 5 } 6 class Program 7 { 8 static void Main(string[] args) 9 {10 //Dictionary<,> 是一個開放類型, 它有兩個類型參數11 Type t = typeof(Dictionary<,>);12 //DictionaryStringKey<int> 是一個封閉類型13 t = typeof(DictionaryStringKey<int>);14 }15 }
1.2.2
泛型中的靜態字段和靜態函數問題
靜態數據類型是屬於類型的. 對於靜態之端來講, 若是某個MyClass類中定義了一個靜態字段X, 則無論以後建立了多少個該類的實例,也無論從該類派生出多少個實例,
都只存在一個MyClass.x字段. 但泛型類型卻並不是如此, 每一個封閉的泛型類型中都有僅屬於他本身的靜態數據.post
1 //泛型類型, 具備一個類型參數 2 public static class TypeWithStaticField<T> 3 { 4 //靜態字段 5 public static string field; 6 //靜態構造函數 7 public static void OutField() 8 { 9 Console.WriteLine(field + ":" + typeof(T).Name);10 }11 }12 13 //非泛型類14 public static class NoGenericTypeWithStaticField15 {16 public static string field;17 public static void OutField()18 {19 Console.WriteLine(field);20 }21 }22 23 class Program24 {25 static void Main(string[] args)26 {27 //使用不一樣類型實參來實例化泛型實例28 TypeWithStaticField<int>.field = "一";29 TypeWithStaticField<string>.field = "二";30 TypeWithStaticField<Guid>.field = "三";31 32 //對於非泛型類型, 此時field只會有一個值, 每次賦值都改變了原來的值33 NoGenericTypeWithStaticField.field = "非泛型類靜態字段一";34 NoGenericTypeWithStaticField.field = "非泛型類靜態字段二";35 NoGenericTypeWithStaticField.field = "非泛型類靜態字段三";36 37 NoGenericTypeWithStaticField.OutField();38 39 //證實每一個封閉類型都有一個靜態字段40 TypeWithStaticField<int>.OutField();41 TypeWithStaticField<string>.OutField();42 TypeWithStaticField<Guid>.OutField();43 Console.ReadKey();44 }45 }
運行結果圖:性能
從圖中能夠看出每一個封閉的泛型類型都有屬於它本身的靜態字段. 泛型暫時就寫這麼多, 之後遇到這方面的內容還會繼續補充.學習
2,可空類型ui
2.1可空類型也是值類型, 但它是包含null值得值類型.
int? nullable = null;
解析: C# 確定沒有int?這個類型, 對於編譯器而言,int?會被編譯成Nullable<int>類型, 便可空類型. C# 2.0 提供和的可空類型是Nullable<int>和Nullable. (可控類型的定義是public struct Nullable<T> where T:struct, T只能爲值類型)
int? value = 1 等價於==> Nullable<int> value = 1;
2.2 空合併操做符
空合併操做符即??操做符, 他會對左右兩個操做數進行判斷: 若是左邊的數不爲null,就返回左邊的數; 若是左邊的數位null, 就返回右邊的數.
這個操做符能夠用於可空類型, 也可用於引用類型,可是不能用於值類型. 由於??運算符會將其左邊的數與null進行比較, 但除了可空類型外,其餘的值類型是不能與null進行比較的.
可空類型的優勢就是能夠很方便地設置默認值,避免了經過if和else語句來進行判斷, 從而簡化代碼函數,提升了代碼的可讀性:
int? nullHasValue = 1;
int x = nullHasValue ?? 12;// ??和三目運算符功能差很少, 相似於: x = nullHasValue.HasValue ? b.value : 12;
2.3 可空類型與一元或二元運算符一塊兒使用時,只要有一個操做數爲null,結果都爲null;
int? d = null;
int? dd = d = 5;
Console.WriteLine(dd); //null
同理: 比較可空類型時,只要一個操做數爲null,比較結果就爲false。
2.4可空類型的裝箱與拆箱
既然值類型存在着裝箱和拆箱, 而可空類型屬於值類型, 那麼它天然也就存在裝箱和拆箱. 當把一個可空類型賦給引用類型變量時, CLR會對可空類型對象處理.
CLR首先會檢測可空類型是否爲null. 若是爲null, CLR將不會進行實際的裝箱操做, 若是不爲null,CLR則會從可空類型對象中獲取值,並對該值進行裝箱操做.
1 //定義一個可控類型對象nullable 2 Nullable<int> nullable = 5; 3 int? nullableWithoutValue = null; 4 5 //得到可空對象的類型, 此時返回的是System.Int32, 而不是System.Nullable<System.Int32>, 這一點須要特別注意 6 nullable.GetType();// System.Int32 7 8 //對一個爲null的類型調用方法時將出現異常, 因此通常引用類型調用方法前, 最好先檢查下它是否爲null 9 nullableWithoutValue.GetType();10 11 //裝箱操做12 object obj = nullable;13 obj.GetType();// System.Int3214 15 //拆箱後變成非可空變量16 int value = (int)obj;17 18 //拆箱後變成可空類型19 nullable = (int?)obj;
前面說了 對於沒有值得可空類型調用函數時會拋出空引用異常, 可是仍然能夠訪問HasValue屬性.
緣由在於,可空類型是包含null值得可空類型, 對於向可空類型賦值這項操做來講, null是一個有效的值類型.而向引用類型賦值null值則表示空引用
表示不指向託管對中的任何對象, 因此能夠訪問HasValue屬性.
3. 匿名方法
匿名方法就是沒有名字的方法. 由於沒有名字, 匿名方法只能在函數定義的時候被調用, 在其餘任何狀況下都不能被調用.
前面講到委託的時候講到 委託是後續諸多特性的基礎, 匿名方法和委託有着莫大的關係. 下面用代碼來講明兩者之間的關係. 首先回顧委託的使用方法.
1 class Program 2 { 3 //定義投票委託 4 delegate void VoteDelegate(string name); 5 static void Main(string[] args) 6 { 7 //使用Vote方法來實例化委託對象 8 VoteDelegate voteDelegate = new VoteDelegate(new Friend().Vote); 9 //下面的方式爲隱式實例化委託方式,它把方法直接賦給了委託對象10 //VoteDelegate voteDelegate = new Friend().Vote;11 12 //經過調用委託來回調Vote()方法, 這是隱式調用方式13 voteDelegate("BarryWang");14 Console.ReadKey();15 }16 17 public class Friend18 { 19 //朋友的投票方法20 public void Vote(string nickName)21 {22 Console.WriteLine("暱稱爲: {0}來辦Wang Meng投票了", nickName);23 }24 }25 }
委託是用來包裝方法的類類型, 既然委託方法也是方法, 固然能夠被委託類型包裝了, 因此咱們還能夠用匿名方法的方式去實現前面的代碼:
1 class Program 2 { 3 //定義投票委託 4 delegate void VoteDelegate(string name); 5 static void Main(string[] args) 6 { 7 //使用Vote方法來實例化委託對象 8 VoteDelegate voteDelegate = delegate(string nickName) 9 {10 Console.WriteLine("暱稱爲: {0}來辦Wang Meng投票了", nickName);11 };12 13 //經過調用委託來回調Vote()方法, 這是隱式調用方式14 voteDelegate("BarryWang");15 Console.ReadKey();16 }17 }
從以上代碼能夠看出, 若使用了匿名方法, 就再也不須要單獨定義一個Vote方法了, 這減小了代碼行數, 更有利於程序閱讀.
可是匿名方法也有缺點: 不能再其餘地方被調用, 即不具備重複性. 因此若是委託包裝的方法相對簡單, 而且該方法在其餘地方的調用頻率較低, 咱們就能夠考慮用匿名方法來實例化委託對象了.
4, 迭代器
迭代器記錄了集合中的某個位置, 它使程序只能向前移動.
在C#1.0中, 一個類中要想使用foreach關鍵字進行遍歷, 它必須實現IEnumerable或者IEnumerable<T>接口.
然而在C#2.0中, 微軟提供了yield關鍵字來簡化迭代器的實現, 這使得自定義迭代器變得容易了不少.
4.1,首先咱們來看看IEnumerable、IEnumerator的區別來幫助咱們理解迭代器:
先來看一下IEnumerable接口,其實看過這個接口以後,發現它實際上是很是的簡單,只包含一個方法GetEnumerator(),它返回一個可用於循環訪問集合的IEnumerator對象,以下面代碼所示:
1 public interface IEnumerable 2 { 3 // Summary: 4 // Returns an enumerator that iterates through a collection. 5 // 6 // Returns: 7 // An System.Collections.IEnumerator object that can be used to iterate through 8 // the collection. 9 [DispId(-4)]10 IEnumerator GetEnumerator();11 }
那麼再來看看IEnumerator中的實現方法:
這裏的IEnumerator對象,其實就是另一個接口,這個接口對象有什麼呢?它是一個真正的集合訪問器,沒有它,就不能使用foreach語句遍歷集合或數組,由於只有IEnumerator對象才能訪問集合中的項,假如連集合中的項都訪問不了,那麼進行集合的循環遍歷是不可能的事情了。那麼讓咱們看看IEnumerator接口又定義了什麼東西。
View Code
那麼咱們再來看一個真實的例子:
1 public class Person 2 { 3 public string Name { get; set; } 4 public int Age { get; set; } 5 } 6 7 public class People : IEnumerable 8 { 9 Person[] personList = new Person[4];10 public People()11 {12 personList[0] = new Person() { Name = "aehyok", Age = 25 };13 personList[1] = new Person() { Name = "Kris", Age = 22 };14 personList[2] = new Person() { Name = "Leo", Age = 21 };15 personList[3] = new Person() { Name = "Niki", Age = 23 };16 }17 18 public IEnumerator GetEnumerator()19 {20 return this.personList.GetEnumerator();21 }22 }23 24 class Program25 {26 static void Main(string[] args)27 {28 People p = new People();29 30 //第一種遍歷Person的方式31 foreach (Person person in p)32 {33 Console.WriteLine("Name {0} : Age {1}", person.Name, person.Age);34 }35 36 //第二種遍歷方式37 IEnumerator i = p.GetEnumerator();38 while (i.MoveNext())39 {40 Person person = (Person)i.Current;41 Console.WriteLine("Name {0} : Age {1}", person.Name, person.Age);42 }43 44 Console.ReadKey();45 }46 }
從上面咱們知道IEnumerator接口定義了一個Current屬性,MoveNext和Reset兩個方法,這是多麼的簡約。既然IEnumerator對象是一個訪問器。那至少應該有一個Current屬性,來獲取當前集合中的項吧。MoveNext方法只是將遊標的內部位置向前移動(就是移到一下個元素而已),要想進行循環遍歷,不向前移動一下怎麼行呢?
經過註釋也能夠明確的發現他們的用處。
4.2, 使用yield自定義迭代器
直接看code的實現形式吧:
View Code
4.3迭代器的執行過程圖解
PS: 這兩天比較閒 便更新的比較頻繁. 寫完這個系列也等於把這本書又從新讀了一遍, 仍有很多的收穫. 勉勵本身多讀書, 多記錄, 加油! 2016/01/20