1 public class Stack<T> 2 { 3 private T[] _Items; 4 5 public void Push(T data) 6 { 7 8 } 9 10 public void Pop() 11 { 12 13 } 14 }
泛型的優點: 1、泛型提供了一個強類型的編程模型。它確保在參數化的類中,只有成員明確希望的數據類型才可以使用。
1 interface IPair<T> 2 { 3 T First { get; set; } 4 T Second { get; set; } 5 } 6 public struct Pair<T> : IPair<T> 7 { 8 private T _First; 9 public T First 10 { 11 get 12 { 13 return _First; 14 } 15 set 16 { 17 _First = value; 18 } 19 } 20 private T _Second; 21 public T Second 22 { 23 get 24 { 25 return _Second; 26 } 27 set 28 { 29 _Second = value; 30 } 31 } 32 33 34 }
注:實現接口時,語法與非泛型類的語法是相同的。然而,如果實現一個泛型接口,同時不指定類型參數,會強迫類成為一個泛型類。 不過此例使用了struct而不是類,表明C#支持自定義的泛型值類型。 對於泛型接口的支持對於集合類來說尤其重要,使用泛型最多的地方就是集合類。假如沒有泛型 ,開發者就要依賴於System.Collections命名空間中的一系列接口。 6、在一個類中多次實現相同的接口 模塊接口造成的另一個結果是,可以使用不同的類型參數來多次實現同一個接口。
1 public interface IContainer<T> 2 { 3 ICollection<T> Items 4 { set; get; } 5 } 6 public class Address 7 { 8 } 9 public class Phone 10 { 11 } 12 public class Email 13 { 14 } 15 16 public class Person : IContainer<Address>, IContainer<Phone>, IContainer<Email> 17 { 18 ICollection<Address> IContainer<Address>.Items 19 { set; get; } 20 21 ICollection<Phone> IContainer<Phone>.Items 22 { set; get; } 23 24 ICollection<Email> IContainer<Email>.Items 25 { set; get; } 26 }
7、構造器和終結器的定義 泛型的構造器和析構器不要求添加類型參數來與類的聲明匹配。
1 2 interface IPair<T> 3 { 4 T First { get; set; } 5 T Second { get; set; } 6 } 7 public struct Pair<T> : IPair<T> 8 { 9 public Pair(T first, T second) 10 { 11 _First = first; 12 _Second = second; 13 } 14 private T _First; 15 public T First 16 { 17 get 18 { 19 return _First; 20 } 21 set 22 { 23 _First = value; 24 } 25 } 26 private T _Second; 27 public T Second 28 { 29 get 30 { 31 return _Second; 32 } 33 set 34 { 35 _Second = value; 36 } 37 } 38 39 }
在構造器當中,必須對所有成員變量進行初始化。 因為一個成員變量在泛型中,有可能是值類型的,也有可能是引用類型的。所以需要顯式賦值。否則統一初始化null是不合適的。 不過可以使用default運算符對任意數據類型的默認值進行動態編碼。
1 public Pair(T first) 2 { 3 _First = first; 4 _Second = default(T); 5 }
注:default運算符允許在泛型的上下文之外使用,任何語句都可以使用它。 8、多個類型參數 泛型類型可以使用任意數量的類型參數,在前面的Pair<T>,只包含一個類型參數,為了存儲不同類型的兩個對象,比如一個"名稱/值"對 ,需要支持兩個或者更多的類型參數。
1 interface IPair<TFirst,TSecond> 2 { 3 TFirst First { get; set; } 4 TSecond Second { get; set; } 5 } 6 public struct Pair<TPFirst, TPSecond> : IPair<TPFirst, TPSecond> 7 { 8 public Pair(TPFirst first,TPSecond second) 9 { 10 _First = first; 11 _Second = second; 12 } 13 private TPFirst _First; 14 public TPFirst First 15 { 16 get 17 { 18 return _First; 19 } 20 set 21 { 22 _First = value; 23 } 24 } 25 private TPSecond _Second; 26 public TPSecond Second 27 { 28 get 29 { 30 return _Second; 31 } 32 set 33 { 34 _Second = value; 35 } 36 } 37 38 }
同樣,只需要在聲明和實例化語句的尖括號中指定多個類型參數,然後提供調用方法時與該方法的參數相匹配的類型。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 Pair<int, string> historicalEvent = new Pair<int, string>(1914, "Shackletion leaves for South Pole on ship Endurance"); 7 Console.WriteLine("{0}:{1}",historicalEvent.First,historicalEvent.Second); 8 9 10 } 11 }
9、元數 在C#4.0中,CLR團隊定義了9個新的泛型類型,它們都叫Touple。和Pair<...>一樣,相同的名稱可以重用,因為它們的元數不同( 每個類都有不同數量的類型參數)。 可以通過元數的不同重載類型定義。 public static class Tuple { // 摘要: // 創建新的 1 元組,即單一實例。 // // 參數: // item1: // 元組僅有的分量的值。 // // 類型參數: // T1: // 元組的唯一一個分量的類型。 // // 返回結果: // 值為 (item1) 的元組。 public static Tuple<T1> Create<T1>(T1 item1); // // 摘要: // 創建新的 2 元組,即二元組。 // // 參數: // item1: // 此元組的第一個分量的值。 // // item2: // 此元組的第二個分量的值。 // // 類型參數: // T1: // 此元組的第一個分量的類型。 // // T2: // 元組的第二個分量的類型。 // // 返回結果: // 值為 (item1, item2) 的 2 元組。 public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2); // // 摘要: // 創建新的 3 元組,即三元組。 // // 參數: // item1: // 此元組的第一個分量的值。 // // item2: // 此元組的第二個分量的值。 // // item3: // 此元組的第三個分量的值。 // // 類型參數: // T1: // 此元組的第一個分量的類型。 // // T2: // 元組的第二個分量的類型。 // // T3: // 元組的第三個分量的類型。 // // 返回結果: // 值為 (item1, item2, item3) 的 3 元組。 public static Tuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3); // // 摘要: // 創建新的 4 元組,即四元組。 // // 參數: // item1: // 此元組的第一個分量的值。 // // item2: // 此元組的第二個分量的值。 // // item3: // 此元組的第三個分量的值。 // // item4: // 此元組的第四個分量的值。 // // 類型參數: // T1: // 此元組的第一個分量的類型。 // // T2: // 元組的第二個分量的類型。 // // T3: // 元組的第三個分量的類型。 // // T4: // 此元組的第四個分量的類型。 // // 返回結果: // 值為 (item1, item2, item3, item4) 的 4 元組。 public static Tuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, T4 item4); // // 摘要: // 創建新的 5 元組,即五元組。 // // 參數: // item1: // 此元組的第一個分量的值。 // // item2: // 此元組的第二個分量的值。 // // item3: // 此元組的第三個分量的值。 // // item4: // 此元組的第四個分量的值。 // // item5: // 此元組的第五個分量的值。 // // 類型參數: // T1: // 此元組的第一個分量的類型。 // // T2: // 元組的第二個分量的類型。 // // T3: // 元組的第三個分量的類型。 // // T4: // 此元組的第四個分量的類型。 // // T5: // 此元組的第五個分量的類型。 // // 返回結果: // 值為 (item1, item2, item3, item4, item5) 的 5 元組。 public static Tuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5); // // 摘要: // 創建新的 6 元組,即六元組。 // // 參數: // item1: // 此元組的第一個分量的值。 // // item2: // 此元組的第二個分量的值。 // // item3: // 此元組的第三個分量的值。 // // item4: // 此元組的第四個分量的值。 // // item5: // 此元組的第五個分量的值。 // // item6: // 此元組的第六個分量的值。 // // 類型參數: // T1: // 此元組的第一個分量的類型。 // // T2: // 元組的第二個分量的類型。 // // T3: // 元組的第三個分量的類型。 // // T4: // 此元組的第四個分量的類型。 // // T5: // 此元組的第五個分量的類型。 // // T6: // 此元組的第六個分量的類型。 // // 返回結果: // 值為 (item1, item2, item3, item4, item5, item6) 的 6 元組。 public static Tuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6); // // 摘要: // 創建新的 7 元組,即七元組。 // // 參數: // item1: // 此元組的第一個分量的值。 // // item2: // 此元組的第二個分量的值。 // // item3: // 此元組的第三個分量的值。 // // item4: // 此元組的第四個分量的值。 // // item5: // 此元組的第五個分量的值。 // // item6: // 此元組的第六個分量的值。 // // item7: // 元組的第七個分量的值。 // // 類型參數: // T1: // 此元組的第一個分量的類型。 // // T2: // 元組的第二個分量的類型。 // // T3: // 元組的第三個分量的類型。 // // T4: // 此元組的第四個分量的類型。 // // T5: // 此元組的第五個分量的類型。 // // T6: // 此元組的第六個分量的類型。 // // T7: // 元組的第七個分量的類型。 // // 返回結果: // 值為 (item1, item2, item3, item4, item5, item6, item7) 的 7 元組。 public static Tuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7); // // 摘要: // 創建新的 8 元組,即八元組。 // // 參數: // item1: // 此元組的第一個分量的值。 // // item2: // 此元組的第二個分量的值。 // // item3: // 此元組的第三個分量的值。 // // item4: // 此元組的第四個分量的值。 // // item5: // 此元組的第五個分量的值。 // // item6: // 此元組的第六個分量的值。 // // item7: // 元組的第七個分量的值。 // // item8: // 元組的第八個分量的值。 // // 類型參數: // T1: // 此元組的第一個分量的類型。 // // T2: // 元組的第二個分量的類型。 // // T3: // 元組的第三個分量的類型。 // // T4: // 此元組的第四個分量的類型。 // // T5: // 此元組的第五個分量的類型。 // // T6: // 此元組的第六個分量的類型。 // // T7: // 元組的第七個分量的類型。 // // T8: // 元組的第八個分量的類型。 // // 返回結果: // 值為 (item1, item2, item3, item4, item5, item6, item7, item8) 的 8 元祖(八元組)。 public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8); } View Code
這一組的Tuple<...>類是出於和Pari<T>與Pair<TPFirst,TPSecond>類相同的目的而設計的。 只是在不特殊處理的前提下,它們最多能同時處理7個類型參數。 當其中一個類型參數實際值為Tuple時,元組的大小實際可以實現無限的。 可以支持普通的創建方法,也支持靜態方法Create來創建。(此為一工廠方法)。 10、嵌套泛型類型 嵌套類型自動獲得包容類型的類型參數。相當於在包容類型當中,這個類型參數是一個新的數據類型,可以和其它數據類型一樣使用。 如果嵌套類型包含了自己的類型參數,會隱藏包容類型當中的同名類型參數。
1 class Container<T, U> 2 { 3 class Nested<U> 4 { 5 void Method(T param0, U param1) 6 { 7 8 } 9 } 10 } 11
注:在合法的情況下,可以將類型參數轉換為指定的接口,或者類。為了保證合法性,就需要一些約束性。 11、約束 泛型允許為類型參數定義約束。這些約束強迫類型遵守各種規則。 C#允許為泛型類中聲明的每個類型參數提供一個可行的約束列表。約束聲明了泛型要求的類型參數的特征。 為了聲明一個約束,需要使用where關鍵字,後跟一對"參數:要求"。其中,"參數"必須是泛型類型中定義的一個參數,而 "要求"用於限制類型從中派生的類或接口,或者限制必須存在一個默認構造器,或者限制使用一個 引用/值 類型約束。 11、1 接口約束
1 public class BinaryTree<T> where T : System.IComparable<T> 2 { 3 4 }
指定,實際使用的類型參數T,必須實現了IComparable<T>接口。如此T的變量也可以直接調用IComparable接口的方法。而不用顯式轉換。 有了這種約束之後,甚至不需要執行轉型,就可以調用一個顯式的接口成員實現。 11、2 基類約束
1 public class BinaryTree<T> where T : Phone 2 { 3 4 }
基類約束的語法與接口約束基本相同。 但是,如果同時指定了多個約束,那麼基類約束必須第一個出現。 而且不允許在一個約束中將參數參數限制為必須從String或者System.Nullable<T>派生。 11、3 struct/class約束 另一個重要的泛型約束是將類型參數限制為一個值類型或者引用類型。
1 public class BinaryTree<T> where T : struct 2 { 3 4 } 5 public class BinaryTree<T> where T : class 6 { 7 8 }
struct約束有一個非常特別的地方。一方面,它規定類型參數只能為值類型;另一方面, 它禁止將System.Nullable<T>作為類型參數。 原因如下:因為假如沒有這個限制,就可以定義毫無意義的Nullable<Nullable<T>>類型。之所以毫無意義, 是因為Nullable<T>本身允許可空的值類型變量,而一個可空的"可空類型"是毫無意義的。 由於可空運算符(?)是C#用於聲明一個可空值類型的快捷方式,因此sturct約束對Nullable<T>的限制同時 會禁止你寫下面這樣的代碼: int ?? number; //等價於Nullable<Nullable<T>> 11、4 多個約束 對於任何給定的類型參數,都可以指定任意數量的接口作為約束,但基類約束只能指定一個, 因為一個類可以實現任意數量的接口,但肯定只能從一個類繼承。 每個新約束都在一個以逗號分隔的列表中聲明,約束列表跟在泛型類型名稱和一個冒號之後。 如果有多個類型參數,那麼每個類型名稱的前面都要使用一個 where 關鍵字
1 public class BinaryTree<T, U> : Container<T, U> 2 where T : IComparable<T>, IFormattable 3 where U : Phone 4 { 5 6 }
注:在where 之間並不存在逗號 11、5 構造器約束 某些情況下,需要在泛型類的內部創建類型參數的一個實例。 所以需要用約束來規定必須有一個默認構造器
1 public class BinaryTree<T, U> : Container<T, U> 2 where T : IComparable<T>, IFormattable 3 where U : Phone,new () 4 { 5 6 }
new()來指定構造器約束。 11、6 約束繼承 約束可以由一個派生類繼承,但必須與基類一樣,需要在派生類中顯式地指定這些約束。 否則會引發編譯錯誤。
1 public class EntityBase<T> where T:IComparable<T> 2 { 3 4 } 5 public class Entity<T> : EntityBase<T> where T : IComparable<T> 6 { 7 8 }
重寫一個虛泛型方法是,或者創建一個顯式接口方法實現時,約束是隱式繼承的,不需要重新聲明。
1 public class EntityBase<T> where T:IComparable<T> 2 { 3 public virtual void Method<T>(T t) 4 where T : IComparable<T> 5 { 6 7 } 8 } 9 public class Entity<T> : EntityBase<T> where T : IComparable<T> 10 { 11 public override void Method<T>(T t) 12 { 13 14 } 15 }
在繼承的情況下,不僅可以保留基類本來的約束(這是必須的),還可添加額外的約束, 從而對基類的類型參數進行進一步的限制。 但是,重寫的成員需要遵守基類方法中定義的“接口”。 額外的約束可能破壞多態性,所以不允許新增約束,而且重寫方法上的參數約束是隱式繼承的。 12 、重點: 約束的限制 待查 1、不允許運算符約束 不能用約束來規定一個類必須支持一個特定的方法或者運算符,除非那個方法 或運算符在一個接口上。 2、不支持OR條件 默認為AND的。如果支持OR,編譯器就無法在編譯時判斷要調用哪一個方法。 3、委托和枚舉類型的約束是無效的 將任何委托類型作為一個類約束來使用是一種不被允許的約束。 所有委托類型都被視為是不能指定為類型參數的特殊類。 4、只允許默認構造器約束 不能要求支持其他非默認的構造器。 13、泛型方法 泛型方法是即使包容類不是泛型類,或者方法包含的類型參數不在泛型類的類型參數列表中, 也依然使用泛型的方法。 為了定義泛型方法,需要緊接在方法名之後添加類型參數語法。
1 public static class MathEx 2 { 3 public static T Max<T>(T first, params T[] values) 4 where T : IComparable<T> 5 { 6 T maximun = first; 7 foreach (T item in values) 8 { 9 if (item.CompareTo(maximun) > 0) 10 { 11 maximun = item; 12 } 13 } 14 return maximun; 15 } 16 }
注:方法可以是靜態,或者是成員方法。 泛型方法和在相似,可以包含多個類型參數。類型參數的數量是一中可用來區分方法簽名的特性。同理類。 13、1 類型推斷 只要類型是隱式兼容的,類型推斷是根據參數來判斷實際使用的參數類型。 從而省略提類型參數。
1 Console.WriteLine(MathEx.Max<int>(7, 490)); 2 Console.WriteLine(MathEx.Max<string>("R.O.U.S.","Fireswamp")); 3 4 Console.WriteLine(MathEx.Max(7, 490)); 5 Console.WriteLine(MathEx.Max("R.O.U.S.","Fireswamp"));
如果類型推斷出錯,可以顯式轉型或者指定類型實參。 13、2 約束的指定 泛型方法也允許指定約束。 約束緊接在方法頭的後面,但位於構成方法主體的大括號之前。
1 public virtual void Method<T>(T t) 2 where T : IComparable<T> 3 { 4 5 }
13、3 泛型方法中的轉型 有時應該避免使用泛型-----例如,在使用它會造成一次轉型操作被“掩蓋”起來的時候。 在方法中執行顯式轉型,相較於在泛型版本中執行隱式轉型,前者能夠更加明確地描述所 發生的事情。開發者在泛型方法中執行轉型時,假如沒有約束來驗證轉型的有效性,需要小心。 14、協變性和逆變性 如果用不同的類型參數聲明同一個泛型類的兩個變量,變量不是類型兼容的,即使是將一個較具體的類型 賦給一個較泛化的類型。也就是說:它們不是協變量。 例如:Contact Address是從PdaItem派生的 雖然兩個類型參數是兼容的,但是Pair<Contact>與Pair<PdaItem> 之間不允許轉換。 Pair<Contact> contacts=new Pair<Contact>((new Contact()); Pair<PdaItem> paditem=new Pair<PdaItem>((new PdaItem()); Pair<Contact>和Pair<PdaItem>這兩個數據類型之間不可以隨便轉換。以及其它類似牽扯到的轉換(比如泛型接口、委托等)。 如:IPair<PdaItem> 與 Pair<Contact> contacts 之間的轉換 協變:從子類往基類轉換(較具體的往較泛化的轉換) 逆變:從基類向子類轉換(較泛化的往較具體的轉換) 注:因為數據項可能是異質的。禁止協變性可維持同質性 待查。 14、1 在C#4.0中使用out類型參數修飾符允許協變性 可能的協變性
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //只有接口和委托可以使用in out修飾 6 Pair<Contact> contacts = new Pair<Contact>(new Contact("Princess Buttercupt", DateTime.Now), 7 new Contact("Inigo Montoya", DateTime.Now)); 8 //IPair<PdaItem> pair = contacts;//因為沒有使用out ,不允許協變 9 IReadOnlyPair<PdaItem> readPair = contacts; 10 //異質:泛型指定的參數數據類型與實際的數據類型不同且非繼承關系() 11 //異質的產生: 12 //因為如果允許從子類往上轉型成了基類,理論上可以改變 pair.ReadOnlyFirst = new Address(); 13 //因為Address是PdaItem的子類 會造成數據的異質 14 //本來只允許包含的是Contact類型(T被指定為Contact),現在卻包含了Address 這個沒有直接關系的類型 15 //所以才需要使用通過out修飾限制泛型類型聲明,讓它只向接口的外部公開數據 16 17 PdaItem pdaItem1 = readPair.ReadOnlyFirst;//只可讀取,不可設置 18 PdaItem pdaItem2 = readPair.ReadOnlySecond; 19 Console.WriteLine(pdaItem1.Name + " " + pdaItem1.LastUpdated); 20 Console.WriteLine(pdaItem2.Name + " " + pdaItem2.LastUpdated); 21 22 23 24 25 26 27 28 } 29 } 30 public class PdaItem 31 { 32 public PdaItem() 33 { 34 } 35 public PdaItem(string pName, DateTime pLastUpdated) 36 { 37 Name = pName; 38 LastUpdated = pLastUpdated; 39 } 40 public virtual string Name { set; get; } 41 42 public DateTime LastUpdated { set; get; } 43 } 44 45 public class Contact : PdaItem 46 { 47 public override string Name 48 { 49 get 50 { 51 return FirtstName; 52 } 53 set 54 { 55 FirtstName = value + " from Contact"; 56 } 57 } 58 private string FirtstName; 59 public Contact() 60 { 61 } 62 public Contact(string pName, DateTime pLastUpdated) 63 : base(pName, pLastUpdated) 64 { 65 66 } 67 68 } 69 70 public class Address : PdaItem 71 { 72 public override string Name 73 { 74 get 75 { 76 return DetailAddress; 77 } 78 set 79 { 80 DetailAddress = value + " from Address"; 81 } 82 } 83 //此處會造成與Contact數據不一,從而造成泛型的數據異質 84 private string DetailAddress; 85 public string address1; 86 public string address2; 87 public string address3; 88 public Address() 89 { 90 } 91 public Address(string pName, DateTime pLastUpdated) 92 : base(pName, pLastUpdated) 93 { 94 95 } 96 97 } 98 99 interface IReadOnlyPair<out T> 100 { 101 T ReadOnlyFirst { get; } 102 T ReadOnlySecond { get; } 103 } 104 interface IPair<T> 105 { 106 T First { get; set; } 107 T Second { get; set; } 108 } 109 public struct Pair<T> : IPair<T>, IReadOnlyPair<T> 110 { 111 112 public Pair(T first, T second) 113 { 114 TPFirst = TPFirstReadOnly = first; 115 TPSecond = TPSecondReadOnly = second; 116 } 117 private T TPFirst; 118 private T TPSecond; 119 private T TPFirstReadOnly; 120 private T TPSecondReadOnly; 121 122 T IPair<T>.First 123 { 124 get 125 { 126 return TPFirst; 127 } 128 set 129 { 130 TPFirst = value; 131 } 132 } 133 T IPair<T>.Second 134 { 135 get 136 { 137 return TPSecond; 138 139 } 140 set 141 { 142 TPSecond = value; 143 } 144 } 145 T IReadOnlyPair<T>.ReadOnlyFirst 146 { 147 get 148 { 149 return TPFirstReadOnly; 150 } 151 } 152 T IReadOnlyPair<T>.ReadOnlySecond 153 { 154 get 155 { 156 return TPSecondReadOnly; 157 } 158 159 } 160 }
注:如果沒有out,就會報錯,通過限制泛型類型聲明,讓它只向接口的外部公開數據,編譯器就沒有理由禁止協變性了。 在C#4.0中,對有效協變性的支持(被賦值的類型只對外公開數據)是通過out類型參數修飾符來添加的。 用out來修飾的類型參數,會導致編譯器驗證T真的只用於成員的返回和屬性的取值方法(get訪問方法),永遠不用於輸入參數或者屬性 的賦值方法(set訪問器方法)。在此之後,編譯器就會允許對接口的任何協變賦值操作了。 14.2 在C#4.0中使用in類型參數修飾符允許逆變性 Pair<PdaItem> pair=new Pair<PdaItem>(new Contact(),new Address()); Pair<Contact> contact = (Pair<Contact>)pair; 逆變性,從 Pair<PdaItem>往Pair<Contact>轉換,但因為項有Contact也有Address 所以完全強制轉換成Contact會無效, 可以通過對泛型接口和委托使用in來實現 假定有一個接口,通過這個接口,只有Contact才 能放到First和Second中,在這種情況下,Pair<PdaItem> 原本存儲的是什麼東西就不重要了(因為這個接口不能 獲取其中的東西,而通過Pair<PdaItem>直接賦值一個 Address也不會影響到這個接口的有效性
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //只有接口和委托可以使用in out修飾 6 Pair<Contact> contacts = new Pair<Contact>(new Contact("Princess Buttercupt", DateTime.Now), 7 new Contact("Inigo Montoya", DateTime.Now)); 8 //IPair<PdaItem> pair = contacts;//因為沒有使用out ,不允許協變 9 IReadOnlyPair<PdaItem> readPair = contacts; 10 //異質:泛型指定的參數數據類型與實際的數據類型不同且非繼承關系() 11 //異質的產生: 12 //因為如果允許從子類往上轉型成了基類,理論上可以改變 pair.ReadOnlyFirst = new Address(); 13 //因為Address是PdaItem的子類 會造成數據的異質 14 //本來只允許包含的是Contact類型(T被指定為Contact),現在卻包含了Address 這個沒有直接關系的類型 15 //所以才需要使用通過out修飾限制泛型類型聲明,讓它只向接口的外部公開數據 16 17 PdaItem pdaItem1 = readPair.ReadOnlyFirst;//只可讀取,不可設置 18 PdaItem pdaItem2 = readPair.ReadOnlySecond; 19 Console.WriteLine(pdaItem1.Name + " " + pdaItem1.LastUpdated); 20 Console.WriteLine(pdaItem2.Name + " " + pdaItem2.LastUpdated); 21 22 23 //從基類轉換成子類的泛型 24 Pair<PdaItem> pdaitem = new Pair<PdaItem>(new Contact("Princess Buttercupt", DateTime.Now), 25 new Address()); 26 IWriteOnlyPair<Contact> writePair = pdaitem; 27 //此處是重點,通過這個接口 1、只能進行賦值且只能是Contact類型或者它的子類,而不能是Address類型(非相關類型) 2、不能進行訪問 28 //這樣就避免了,當pdaitem有多個不同類型的項時,使用一個泛型(T為子類類型)接口訪問數據時,產生錯誤。因為類型不同,內部的成員變量和方法也有可能不同。 29 30 writePair.WriteOnlyFirst = new Contact(); 31 writePair.WriteOnlySecond = new Contact(); 32 33 34 } 35 } 36 public class PdaItem 37 { 38 public PdaItem() 39 { 40 } 41 public PdaItem(string pName, DateTime pLastUpdated) 42 { 43 Name = pName; 44 LastUpdated = pLastUpdated; 45 } 46 public virtual string Name { set; get; } 47 48 public DateTime LastUpdated { set; get; } 49 } 50 51 public class Contact : PdaItem 52 { 53 public override string Name 54 { 55 get 56 { 57 return FirtstName; 58 } 59 set 60 { 61 FirtstName = value + " from Contact"; 62 } 63 } 64 private string FirtstName; 65 public Contact() 66 { 67 } 68 public Contact(string pName, DateTime pLastUpdated) 69 : base(pName, pLastUpdated) 70 { 71 72 } 73 74 } 75 76 public class Address : PdaItem 77 { 78 public override string Name 79 { 80 get 81 { 82 return DetailAddress; 83 } 84 set 85 { 86 DetailAddress = value + " from Address"; 87 } 88 } 89 //此處會造成與Contact數據不一,從而造成泛型的數據異質 90 private string DetailAddress; 91 public string address1; 92 public string address2; 93 public string address3; 94 public Address() 95 { 96 } 97 public Address(string pName, DateTime pLastUpdated) 98 : base(pName, pLastUpdated) 99 { 100 101 } 102 103 } 104 105 interface IReadOnlyPair<out T> 106 { 107 T ReadOnlyFirst { get; } 108 T ReadOnlySecond { get; } 109 } 110 interface IWriteOnlyPair<in T> 111 { 112 T WriteOnlyFirst { set; } 113 T WriteOnlySecond { set; } 114 } 115 interface IPair<T> 116 { 117 T First { get; set; } 118 T Second { get; set; } 119 } 120 public struct Pair<T> : IPair<T>, IReadOnlyPair<T>, IWriteOnlyPair<T> 121 { 122 123 public Pair(T first, T second) 124 { 125 TPFirst = TPFirstWriteOnly = TPFirstReadOnly = first; 126 TPSecond = TPSecondWriteOnly = TPSecondReadOnly = second; 127 } 128 private T TPFirst; 129 private T TPSecond; 130 private T TPFirstReadOnly; 131 private T TPSecondReadOnly; 132 133 private T TPFirstWriteOnly; 134 private T TPSecondWriteOnly; 135 136 //以下都是顯式實現接口,使用時,需要進行顯式轉換才可以使用 137 T IPair<T>.First 138 { 139 get 140 { 141 return TPFirst; 142 } 143 set 144 { 145 TPFirst = value; 146 } 147 } 148 T IPair<T>.Second 149 { 150 get 151 { 152 return TPSecond; 153 154 } 155 set 156 { 157 TPSecond = value; 158 } 159 } 160 T IReadOnlyPair<T>.ReadOnlyFirst 161 { 162 get 163 { 164 return TPFirstReadOnly; 165 } 166 } 167 T IReadOnlyPair<T>.ReadOnlySecond 168 { 169 get 170 { 171 return TPSecondReadOnly; 172 } 173 174 } 175 T IWriteOnlyPair<T>.WriteOnlyFirst 176 { 177 set 178 { 179 TPFirstWriteOnly = value; 180 } 181 } 182 T IWriteOnlyPair<T>.WriteOnlySecond 183 { 184 set 185 { 186 TPSecondWriteOnly = value; 187 } 188 189 } 190 }
14、3 協變性和逆變性類型修飾符可以用在同一個接口上
1 interface IConvertible<in TSource, out TTarget> 2 { 3 TTarget Convert(TSource s); 4 }
通過以上接口,就可以成功地從一個 IConvertible<PdaItem,Contact>轉換成一個IConvertible<Contact,PdaItem> 15 泛型的內部機制 泛型類的"類型參數"變成了元數據,“運行時” 在需要的時候會利用它們構造恰當的類。所以泛型 支持繼承、多態性以及封裝。 使用泛型時,可以定義方法、屬性、字段、類、接口以及委托。 15、1 基於值類型的泛型的實例化 使用一個值類型作為類型參數來首次構造一個泛型類型時, “運行時”會將指定的類型參數放到CIL中合適的位置,從而創建一個 具體化的泛型類型。所以,“運行時”會為每個新的參數值類型創建新的 具體化泛型類型。 比如: 第一次使用Stack<int>類型時,“運行時”會生成Stack類的一個具體版本, 並用int替換它的類型參數。從這以後,每當代碼使用Stack<int>的時候, “運行時”都會重用已生成的具體化Stack<int>類。 使用具體化的值類型的類,好處在於能獲得較好的性能。 除此之外,代碼能避免轉換和裝箱,因為每個具體的泛型都“天生地”包含值類型。 15、2 基於引用類型的泛型實例化 使用一個引用類型作為類型參數來首次構造一個泛型類型時, “運行時”會創建一個具體化的泛型類型,並在CIL代碼中用object引用 ,而不是基於類型參數的一個具體泛型類型)替換類型參數。 以後會相同模板的泛型會使用這同一個類。用類型的泛型類創建的具體化類被減少到了一個 ,所以泛型極大地減少了代碼量。