程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 十一、C# 泛型,

十一、C# 泛型,

編輯:C#入門知識

十一、C# 泛型,


為了促進代碼重用,尤其是算法的重用,C#支持一個名為泛型的特性。 泛型與模塊類相似。 泛型使算法和模式只需要實現一交。而不必為每個類型都實現一次。在實例化的時候,傳入相應的數據類型便可。 注:可空值類型  也是基於泛型實現的。 泛型的本質 就是給某些復雜的類型,抽象一套數據類型的別名。然後可以在這復雜類型當中使用這些別名。 當運行時,可以通過為這一套抽象的數據類型指定實際的數據類型。 1、概述 利用泛型,可以在聲明變量時創建用來處理特定類型的特殊數據結構。程序員定義這種參數化類型, 使特定泛型類型的每個變量都有相同的內部算法,但數據類型和方法簽名可以根據程序員的偏愛而不同。   語法也與C++模塊相似。 所以在C#中,泛型類和結構的語法要求使用相同的尖括號表示法來表示泛型聲明要處理的數據類型。   2、簡單泛型類的定義 在類聲明之後,需要在一對尖括號中指定一個類型參數標識符或者類型參數。

 

 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、泛型提供了一個強類型的編程模型。它確保在參數化的類中,只有成員明確希望的數據類型才可以使用。
2、編譯時類型檢查減少了在運行時發生InvalidCastException異常的幾率。
3、為泛型類成員使用值類型,不再造成object的類型轉換,它們不再需要裝箱操作。
4、C#中的泛型緩解了代碼膨脹的情況。
5、性能得到了提高。一個原因是不再需要從object的強制轉換,從而避免了類型檢查。
另一個是不再需要為值類型執行裝箱。
6、泛型減少了內存消耗。由於避免了裝箱,因此減少了堆上的內存的消耗。
7、代碼的可讀性更好。
8、支持IntelliSense的代碼編輯器現在能直接處理來自泛型類的返回參數。沒有必要為了使IntelliSense工作起來,而對返回
數據執行轉型。     4、類型參數命名的指導原則 和方法參數的命名相似,類型參數的命名應該盡量具有描述性。 除此之外,為了強調它是一個類型參數,名稱就包含一個T前綴。     5、泛型接口與struct C#2.0支持在C#語言中全面地使用泛型,其中包括接口和struct。 語法和類使用的語法完全相同。 要定義包含類型參數的一個接口,將類型參數放到一對尖括號中即可。     
 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引用 ,而不是基於類型參數的一個具體泛型類型)替換類型參數。 以後會相同模板的泛型會使用這同一個類。用類型的泛型類創建的具體化類被減少到了一個 ,所以泛型極大地減少了代碼量。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved