C# 常用接口學習 IEnumerable


作者:烏龍哈里
時間:2015-10-24
平台:Window7 64bit,Visual Studio Community 2015

本文參考:

本文章節:

  • 接口IEnumerable實現
  • 接口IEnumerable<T>實現

正文:

本文是作者摸索學習.Net的過程,逐步進行,比較繁瑣,是作者本人用來幫助記憶的博文。

我們先去看看公開的.Net4.0的源程序中IEnumerable<T>、IEnumerable、IEnumerator<T>和IEnumerator這四個接口是如何聲明的:

    public interface IEnumerable<out T> : IEnumerable
    {
        new IEnumerator<T> GetEnumerator();
    }

    public interface IEnumerator<out T> : IDisposable, IEnumerator
    {
        new T Current {
            get;
        }
    }

    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }

    public interface IEnumerator
    {
        bool MoveNext();
        Object Current {
            get;
        }
    
        void Reset();
    }

 

一、接口IEnumerable實現

1、建一個學生數據結構和一個學生集合類:

    //student數據結構
    class Student
    {
        public int id;
        public string name;
    }

    //student 集合
    class StudentCollection
    {
        public List<Student> students = new List<Student>();
        public void Add(Student student)
        {
            students.Add(student);
        }
    }

公開一個Add()方法以添加數據,我們的集合類建立完畢。下來添加數據:

        static void Main(string[] args)
        {
            StudentCollection sc = new StudentCollection();
            sc.Add(new Student { id=0,name="Tony"});
            sc.Add(new Student { id=1,name="Micheal"});
            sc.Add(new Student { id =2, name = "Amy" });
            foreach(var s in sc) {...}
        }
    }

當我們想用foreach()遍歷的時候,編譯器會告訴我們StudentCollection不包含GetEnumerator,不能用foreach遍歷。雖然StudentCollection里面有能用遍歷的List<T>,但我們不想在屬性上迭代,我們想在類上迭代,不能 foreach(var s in sc.students){...}

現在只有把我們的StudentCollection類改造成能foreach的。

2、繼承接口IEnumerable:

當我們在類后面加上:IEnumerable后,Visual Studio IDE會冒出來一個小黃燈泡,點進去有提示自動填充接口的約定,我們選第一項實現接口(Visaul Studio是全世界最貼心的IDE!),IDE會幫我們把SudentCollection改造成以下的:

    class StudentCollection:IEnumerable
    {
        public List<Student> students = new List<Student>();
        public void Add(Student student)
        {
            students.Add(student);
        }

        public IEnumerator GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

加了一個返回迭代器的方法GetEnumrator。下來按照IEnumetator接口的約定來實現我們的迭代器StudentCollectionEnumerator,用IDE自動補全代碼如下:

    //迭代器
    class StudentCollectionEnumerator : IEnumerator
    {
        public object Current
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public bool MoveNext()
        {
            throw new NotImplementedException();
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }

我的理解是:Current返回當前元素,MoveNext移動到下一個,Reset回到第一個元素。但根據MSDN上面的說法,Reset 方法提供的 COM 互操作性。它不一定需要實現;相反,實施者只需拋出NotSupportedException。但是,如果您選擇執行此操作,則應確保沒有調用方依賴於Reset功能。

迭代器工作的原理是:先調用MoveNext()方法,然后讀取Current得到元素,直到MoveNext返回false。

我們需要3個字段分別放置 元素的位置、元素、元素集。改變后的程序如下:

    //迭代器
    class StudentCollectionEnumerator : IEnumerator
    {
        private int _index;
        private List<Student> _collection;
        private Student value;
        public StudentCollectionEnumerator(List<Student> colletion)
        {
            _collection = colletion;
            _index = -1;
        }
         object IEnumerator.Current
        {
            get { return value; }
        }
        public bool MoveNext()
        {
            _index++;
            if (_index >= _collection.Count) { return false; }
            else { value = _collection[_index]; }
            return true;
        }
        public void Reset()
        {
            _index = -1;
        }

    }

首先,迭代器初始化,引入元素集 _collection,並把索引 _index設置成-1。設置成-1而不是0是因為迭代器首先調用MoveNext,在MoveNext里面我們先把索引+1指向下一個元素,如果索引_index的值初始為0,則第一個元素是元素集[1],第二個元素了。
其次,我們要把object Current改成 IEnumerator.Current,這個是實現迭代器的關鍵。返回元素。(好像有裝箱的行為)
第三,在MoveNext方法內累加索引,並從元素集中讀取元素。然后讓索引值超出元素集返回個false值。
最后,在Reset方法內讓索引值為-1,不過好像直接拋出錯誤也成。

迭代器寫好了,我們在StudentColletion類里面調用:

class StudentCollection : IEnumerable
    {
        public List students;
        public StudentCollection()
        {
            students = new List();
        }
        public void Add(Student student)
        {
            students.Add(student);
        }
        public IEnumerator GetEnumerator()
        {
            return new StudentCollectionEnumerator(students);
        }
    }

測試運行一下,大功告成!我們實現了可枚舉的自己的類。

通過觀察,發現迭代器主要就是返回一個元素對象,而StudentColletion里面的students元素集是List的,本身就能枚舉,我們能不能利用這個不用專門寫迭代器來實現枚舉呢?
答案是肯定的,我們這樣寫:

    class StudentCollection:IEnumerable
    {
        public List<Student> students = new List<Student>();
        public void Add(Student student)
        {
            students.Add(student);
        }

        public IEnumerator GetEnumerator()
        {
            foreach(var s in students)
            {
                yield return s;
            }
        }
    }

這樣就能實現枚舉了,真簡單,充分利用了.Net給出的各種可枚舉集合,不用再去寫GetEnumerator這種累活了。

二、接口IEnumerable<T>實現

如果我們想寫一個通用的可foreach的類,用到泛型:

    class MyCollection<T>
    {
        public List<T> mycollection = new List<T>();
        public void Add(T value)
        {
            mycollection.Add(value);
        }
    }

其實這個MyCollection類只不過是在List<T>外面封裝了一層,要實現IEnumable<T>,繼承該泛型接口,Visual Studio 的IDE自動幫我們補全后,如下:

    class MyCollection:IEnumerable
    {
        public List mycollection = new List();
        public void Add(T value)
        {
            mycollection.Add(value);
        }
        public IEnumerator GetEnumerator()
        {
            throw new NotImplementedException();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

我們直接用上面第二個簡單的寫法,改成:

    class MyCollection:IEnumerable
    {
        public List mycollection = new List();
        public void Add(T value)
        {
            mycollection.Add(value);
        }
        public IEnumerator GetEnumerator()
        {
            foreach(var s in mycollection)
            {
                yield return s;
            }
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (var s in mycollection)
            {
                yield return s;
            }
        }
    }

測試運行:

     static void Main(string[] args)
        {
            MyCollection mc = new MyCollection();
            mc.Add(0);
            mc.Add(1);
            mc.Add(2);
            foreach(var s in mc) { Console.WriteLine(s); }
            Console.ReadKey();
    }

大功告成!
雖然第二種寫法比較投機,充分利用了.NET Framework給的各種泛型集合可枚舉的特征。不過我們也自己實現了一個GetEnumerator(),了解了枚舉器的工作原理。本章學習目的達成。

 


免責聲明!

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



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