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

C# -- 泛型(3)

編輯:C#入門知識

簡介:     前兩篇文章講了關於泛型的一些基礎,下面筆者通過這篇文章來給剛剛接觸泛型的朋友介紹一下     <1>.原理性的東西----” 泛型的協變和逆變 “     <2>.以及常用的接口----” IEnumerable 及其泛型版的IEnumerable<out T> “   ------------------------------------------------------------------------------------------------------------------------------------------------------------           <泛型的協變與逆變|泛型修飾符‘out’與‘in’>   |首先這2個拗口的名詞先不用去管它,先知道協變和逆變主要是用在泛型的接口和委托上就可以了,下面我們通過一個例子來看看:   |在這之前我們插點別的東西,我們知道接口是可以體現多態的,當然接口體現的多態注重的功能上的多態,這和抽象類不同,抽象類更注重的是建立在血緣關系上的多態。   知道接口是可以體現多態的之後,我們來看看一個相關的例子--   鳥和飛機都會飛,把飛定義成一個借口,在定義2個類   復制代碼     public interface IFlyable     {         void fly();     }     class Bird:IFlyable     {         public void fly()         {             Console.WriteLine("鳥兒飛!");         }     }     class Plane:IFlyable     {         public void fly()         {             Console.WriteLine("飛機飛!");         }     } 復制代碼     下面看看接口體現的多態性:   復制代碼             IFlyable ifly;                           ifly = new Bird();             ifly.fly();               ifly = new Plane();             ifly.fly(); 復制代碼 運行結果:   鳥兒飛!   飛機飛!       了解了接口的多態性後我們再來看一個例子:   這裡定義了2個類 Animal 和 Cat (Cat繼承了Animal)   復制代碼     public class Animal     {     }       public class Cat:Animal     {     } 復制代碼     繼續往下看:   Cat cat = new Cat();     下面這句代碼,cat向animal轉,子類向父類轉換,這時cat會隱式轉換為animal 我們說“兒子像父親” 這是完全可以理解的   Animal animal = cat;     但是 說”父親像兒子“ 這是說不過去的 ,但是有的時候如果兒子坑爹,強制轉換了一下還是可以的   cat = (Cat)animal;         (協變)               List<Cat> catArray = new List<Cat>();             List<Animal> animalArray = catArray;     如果是上面說的類,這樣寫是可以的,但是這裡是會報錯的  如圖       繼續往下看 這樣寫卻可以               IEnumerable<Cat> lCat = new List<Cat>();             IEnumerable<Animal> lAnimal = lCat;     對 IEnumerable<Cat> 轉到定義 如圖 我們發現這裡多了一個 “out” 關鍵字       概念引入:   1.對於泛型類型參數,out 關鍵字指定該類型參數是協變的。 可以在泛型接口和委托中使用 out 關鍵字。“協變”是指能夠使用與原始指定的派生類型相比,派生程度更大的類型。   --對於 “協變” 筆者是這樣理解的就是”說的通變化“ 就像 “兒子像父親一樣”(假定父親派生程度0那麼兒子的派生程度就是1了,所以父親可以使用派生程度更大的兒子)   協變與多態性類似,因此它看起來非常自然。       (逆變)   我們知道IComparable<T>接口中,T的修飾符是‘in’,下面我們修改一下上面的代碼演示一下        復制代碼     class Cat : Animal, IComparable<Cat>     {         //僅演示         public int CompareTo(Cat other)         {             return 1;         }     }       class Animal : IComparable<Animal>     {         //僅演示         public int CompareTo(Animal other)         {             return 1;         }     } 復制代碼         這裡Cat和Animal都實現了IComparable<T>接口,然後我們這樣寫               IComparable<Cat> ICat = new Cat();             IComparable<Animal> IAnimal = new Animal();             ICat = IAnimal;     代碼中ICat(高派生程度)使用 IAnimal(低派生程度) “父親像兒子” 和上面的例子完全相反。       概念引入:   2.對於泛型類型參數,in 關鍵字指定該類型參數是逆變的。 可以在泛型接口和委托中使用 in 關鍵字。“逆變”則是指能夠使用派生程度更小的類型。   --對於 “逆變” 筆者的理解則是 “坑爹兒子” 反過來硬說 “父親像兒子” 這是 “說不過去的” 只是利用了強硬的手段           在了解了上面的內容後,我們來看看“out” 與 “in” 關鍵字的特性   IEnumerable<T>接口的IEnumerator<T> GetEnumerator()方法返回了一個迭代器 ,不難發現T如果用 out 標記,則T代表了輸出,也就說只能作為結果返回。   IComparable<T>接口的CompareTo(T other)方法傳入了一個T類型的Other參數,不難發現T如果用 in 標記,則T代表了輸入,也就是它只能作為參數傳入。   下面我們演示一個例子   將動物會叫這功能,定義成一個泛型借口用 out 修飾       這裡會出現一個錯誤       把第二個帶參數的setSound方法,去掉後編譯可以正常通過   下面我們把 out 改成 in       這裡會出現一個錯誤       把第一個setSound方法,去掉後編譯可以正常通過,或者把第一個方法的返回值,改成其它非T類型,編譯也可通過   這個演示充分說明了:out 修飾 T 則 T只能作為結果輸出而不能作為參數  ; in 修飾 T 則 T只能作為參數而不能作為結果返回;       ------------------------------------------------------------------------------------------------------------------------------------------------------------           <IEnumerable接口及其泛型版>    為什麼要用IEnumerable接口? 下面我們通過一個例子看看:   復制代碼     //定義Person類     public class Person     {         public Person(string _name)         {             this.name = _name;         }           public string name;     }       //定義People類     public class People     {         private Person[] _people;           public People(Person[] pArray)         {             //實例化數組 用於存Person實例             _people = new Person[pArray.Length];               for (int i = 0; i < pArray.Length; i++)             {                 _people[i] = pArray[i];             }         }     } 復制代碼 上面的代碼我們定義了一個 Person 類和一個 People 類,顯然 People是用來存放多個Person實例的集合,下面我們嘗試用 Foreeach 遍歷集合的每個元素 輸出:   復制代碼         static void Main(string[] args)         {             Person[] personArray = new Person[3]{             new Person("Keiling1"),             new Person("Keiling2"),             new Person("Keiling3"),             };               People people = new People(personArray);             foreach (Person item in people)             {                 Console.WriteLine(item.name);             }         } 復制代碼 這裡編譯不能通過,出現了一個錯誤       GetEnumerator:是IEnumerable接口中的一個方法,它返回一個 IEnumerator(迭代器),如下圖        IEnumerator內部規定了,實現一個迭代器的所有基本方法,包括 如下圖       為了在foreach中使用 People的實例, 我們給People實現IEnumerable接口,代碼如下:   復制代碼     public class People:IEnumerable     {         private Person[] _people;           public People(Person[] pArray)         {             //實例化數組 用於存Person實例             _people = new Person[pArray.Length];               for (int i = 0; i < pArray.Length; i++)             {                 _people[i] = pArray[i];             }         }   ////IEnumerable和IEnumerator通過IEnumerable的GetEnumerator()方法建立了連接,可以通過IEnumerable的GetEnumerator()得到IEnumerator對象。         IEnumerator IEnumerable.GetEnumerator()         {             return (IEnumerator)GetEnumerator();         }           public PeopleEnum GetEnumerator()         {             return new PeopleEnum(_people);         }     }       public class PeopleEnum:IEnumerator     {         public Person[] _people;           public PeopleEnum(Person [] pArray)         {             _people = pArray;         }         //游標         int position = -1;           //是否可以往下 移         public bool MoveNext()         {             position++;             return (position < _people.Length);         }           //集合的所有元素取完了之後 重置position         public void Reset()         {             position = -1;         }           //實現 IEnumerator的 Current方法 返回當前所指的Person對象         object IEnumerator.Current         {             get             {                 return Current;             }         }           //Current是返回Person類實例的只讀方法         public Person Current         {             get             {                 try                 {                     return _people[position];                 }                 catch (IndexOutOfRangeException)                 {                     throw new InvalidOperationException();                 }             }         }     } 復制代碼 測試運行:   復制代碼         static void Main(string[] args)         {             Person[] personArray = new Person[3]{             new Person("Keiling1"),             new Person("Keiling2"),             new Person("Keiling3"),             };               People people = new People(personArray);             foreach (Person item in people)             {                 Console.WriteLine(item.name);             }         } 復制代碼 結果:       總結:   1.一個集合要支持foreach方式的遍歷,必須實現IEnumerable接口,描述這類實現了該接口的對象,我們叫它 ‘序列’。   比如 List<T> 支持 foreach 遍歷 是因為它實現了IEnumerable接口和其泛型版,如圖--               2. IEnumerator對象具體實現了迭代器(通過MoveNext(),Reset(),Current)。       3. 從這兩個接口的用詞選擇上,也可以看出其不同:IEnumerable是一個聲明式的接口,聲明實現該接口的class是“可枚舉(enumerable)”的,但並沒有說明如何實現迭代器,   而IEnumerator是一個實現式的接口,IEnumerator對象就是一個迭代器。        關於IEnumerable<T>我們來了解一下它的代碼:       4.由於IEnumerable<T>繼承了IEnumerable接口,所以要實現IEnumerator<T> ,還需要實現IEnumerator接口,由於和泛型版本的方法同名,所以該方法的實現需要使用顯式接口實現。這裡就不繼續介紹它的具體實現了,和IEnumerator基本一致,這裡就不詳述了,讀者可以自己動手寫一下。   ps 了解IEnumerable和IEnumerable<T>對今後學西理解LINQ是有很大幫助的。  

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