C# -- 泛型(3)


簡介:

  前兩篇文章講了關於泛型的一些基礎,下面筆者通過這篇文章來給剛剛接觸泛型的朋友介紹一下

  <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是有很大幫助的。

 

 

出自: Keiling_J'Blog http://www.cnblogs.com/keiling/

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM