C#枚舉數和迭代器

大道至簡,始終認爲簡潔是一門優秀的編程語言的一個必要條件。相對來講,C#是比較簡潔的,也愈來愈簡潔。在C#中,一個關鍵字或者語法糖在編譯器層面爲咱們作了不少乏味的工做,可能實現的是一個設計模式,甚至是一個算法。例如:lock關鍵字讓用對象獲取互斥鎖從而實現線程同步,本質上是經過Monitor類來實現的,顯然簡潔不少。本文要講的枚舉數和迭代器在.net集合類被普遍使用,固然遵循着簡潔的設計思想。算法

1.枚舉數

1.1foreach的本質

咱們知道,實現了IEnumerable接口的類型對象是可foreach遍歷的,那麼本質是什麼呢?原來,在IEnumerable接口中定義這樣一個方法:IEnumerator GetEnumerator(),編程

IEnumerator接口定義以下:設計模式

public interface IEnumerator
    {
        // 摘要:
        //     獲取集合中的當前元素。
        //
        // 返回結果:
        //     集合中的當前元素。
        //
        // 異常:
        //   System.InvalidOperationException:
        //     枚舉數定位在該集合的第一個元素以前或最後一個元素以後。
        object Current { get; }

        // 摘要:
        //     將枚舉數推動到集合的下一個元素。
        //
        // 返回結果:
        //     若是枚舉數成功地推動到下一個元素,則爲 true;若是枚舉數越過集合的結尾,則爲 false。
        //
        // 異常:
        //   System.InvalidOperationException:
        //     在建立了枚舉數後集合被修改了。
        bool MoveNext();
        //
        // 摘要:
        //     將枚舉數設置爲其初始位置,該位置位於集合中第一個元素以前。
        //
        // 異常:
        //   System.InvalidOperationException:
        //     在建立了枚舉數後集合被修改了。
        void Reset();
    }
View Code

經過GetEnumerator方法,IEnumerable接口類型對象能夠按需獲取一個枚舉數對象,枚舉數可依次返回請求的集合元素做爲迭代變量。數組

1.2枚舉數的幾種形式

上面說到實現了IEnumerable接口的類型對象是可foreach遍歷的,這是充分沒必要要條件,實際上實現了IEnumerator GetEnumerator()方法的類型對象都是可枚舉的。這裏一共有三種形式:安全

  • IEnumerator/IEnumerable非泛型接口形式
  • IEnumerator<T>/IEnumerable<T>泛型接口形式
  • 自實現形式

1.2.1IEnumerator/IEnumerable非泛型接口形式

 咱們來簡單實現一下:編程語言

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            EnumerableEx arr = new object[] { "jello", 22, 'M' };
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
    }

    class EnumerableEx : IEnumerable
    {
        object[] arr;

        public static implicit operator EnumerableEx(object[] _arr)
        {
            EnumerableEx _enum = new EnumerableEx();
            _enum.arr = new object[_arr.Length];
            for (int i = 0; i < _arr.Length; i++)
            {
                _enum.arr[i] = _arr[i];
            }
            return _enum;
        }

        public IEnumerator GetEnumerator()
        {
            return new EnumeratorEx(arr);
        }
    }

    class EnumeratorEx : IEnumerator
    {
        private int _pos = -1;//當前元素位置
        private object[] _array;//要遍歷的數組
        
        //構造函數
        public EnumeratorEx(object[] array)
        {
            _array = array;
        }
        //迭代變量
        public object Current
        {
            get 
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }
        //移位
        public bool MoveNext()
        {
            if (_pos < _array.Length - 1)
            {
                _pos++;
                return true;
            }
            else
                return false;
        }
        //重置
        public void Reset()
        {
            _pos = -1;
        }
    }

}
View Code

這裏首先向IEnumerable提供了需遍歷的數組(使用了隱式用戶自定義轉換),在foreach中首先會調用GetEnumerator方法,而後MoveNext移到下一個位置,Current即爲迭代變量。ide

1.2.2IEnumerator<T>/IEnumerable<T>泛型接口形式

非泛型接口形式中迭代變量是Object類型(非類型安全),這沒法避免裝箱和拆箱,尤爲是當元素個數不少的時候,性能會消耗很大,所以引入了泛型接口形式。函數

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program1
    {
        public static void Main(string[] args)
        {
            EnumerableEx<int> arr = new int[] { 1, 2, 3 };
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
    }

    class EnumerableEx<T> : IEnumerable<T>
    {
        T[] arr;

        public static implicit operator EnumerableEx<T>(T[] _arr)
        {
            EnumerableEx<T> _enum = new EnumerableEx<T>();
            _enum.arr = new T[_arr.Length];
            for (int i = 0; i < _arr.Length; i++)
            {
                _enum.arr[i] = _arr[i];
            }
            return _enum;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new EnumeratorEx<T>(arr);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new EnumeratorEx<T>(arr);
        }
    }


    class EnumeratorEx<T> : IEnumerator<T>
    {
        private int _pos = -1;//當前元素位置
        private T[] _array;//要遍歷的數組

        public EnumeratorEx(T[] array)
        {
            _array = array;
        }

        public T Current
        {
            get
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }

        public void Dispose()
        {
            //可用於釋放非託管資源
        }

        object System.Collections.IEnumerator.Current
        {
            get 
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }

        public bool MoveNext()
        {
            if (_pos < _array.Length - 1)
            {
                _pos++;
                return true;
            }
            else
                return false;
        }

        public void Reset()
        {
            _pos = -1;
        }
    }

}
View Code

和非泛型接口形式基本同樣,IEnumerable<T>除了繼承IEnumerable接口,還繼承了IDisposable接口用來釋放非託管資源。性能

1.2.3自實現形式

自實現形式不繼承自上面的接口,自定義一個實現GetEnumerator()的類和一個實現Current和MoveNext的類,好處是更加靈活,缺點是通用性差。spa

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace ConsoleApplication1
{
    class Program2
    {
        public static void Main(string[] args)
        {
            MyEnumerable<int> arr = new int[] { 1, 2, 3 };
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
    }
    class MyEnumerable<T>
    {
        T[] arr;

        public static implicit operator MyEnumerable<T>(T[] _arr)
        {
            MyEnumerable<T> _enum = new MyEnumerable<T>();
            _enum.arr = new T[_arr.Length];
            for (int i = 0; i < _arr.Length; i++)
            {
                _enum.arr[i] = _arr[i];
            }
            return _enum;
        }

        public MyEnumerator<T> GetEnumerator()
        {
            return new MyEnumerator<T>(arr);
        }
    }
    class MyEnumerator<T>
    {
        private int _pos = -1;//當前元素位置
        private T[] _array;//要遍歷的數組

        public MyEnumerator(T[] array)
        {
            _array = array;
        }

        public T Current
        {
            get
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }

        public bool MoveNext()
        {
            if (_pos < _array.Length - 1)
            {
                _pos++;
                return true;
            }
            else
                return false;
        }

        public void Reset()
        {
            _pos = -1;
        }
    }
}
View Code

須要注意的是:Reset方法並非必需要實現的。

2.迭代器

2.1What's 迭代器

迭代器是在.net2.0中引入的一種結構,旨在更加簡單地建立枚舉數和可枚舉類型。先來看一個簡單例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program3
    {
        public static void Main(string[] args)
        {
            Iteration iteration = new Iteration();
            foreach (var item in iteration)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
        
    }
    class Iteration
    {
        public IEnumerator<int> GetNums()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i;
            }
        }

        public IEnumerator<int> GetEnumerator()
        {
            return GetNums();
        }
    }
}
View Code

上面是經過yield return來獲取枚舉數的,經過運行結果發現,循環體內的yield return並無在第一次迭代中返回,而是每次訪問迭代變量時都能獲取一個新元素值。

由一個或多個yield語句組成的代碼塊稱爲迭代器塊,它和普通的代碼塊不一樣,並非依次執行的,僅當須要獲取迭代變量值時執行一次。

上面舉了一個使用迭代器來建立枚舉數的例子,其實,使用迭代器還能夠建立可枚舉類型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program3
    {
        public static void Main(string[] args)
        {
            Iteration iteration = new Iteration();
            foreach (var item in iteration)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
        
    }
    class Iteration
    {
        public IEnumerable<int> GetNums()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i;
            }
        }

        public IEnumerator<int> GetEnumerator()
        {
            return GetNums().GetEnumerator();
        }
    }
}
View Code

上面的兩段代碼有兩個地方的不一樣:一是GetNums方法返回類型不一樣;二是GetEnumerator方法實現的不一樣。

2.2迭代器本質

咱們使用簡單的yield return就能夠建立枚舉數或可枚舉類型,那麼在編譯器層面究竟作了些什麼呢?經過IL代碼能夠管中窺豹:

原來,編譯器在遇到迭代器塊時會生成一個嵌套類,這個類實現了IEnumerable<T>和IEnumerator<T>等接口,在這個類中維護了一個擁有四個狀態的狀態機:

  1. Before:首次調用MoveNext的初始狀態
  2. Running:調用MoveNext後進入該狀態。在這個狀態中,枚舉數檢測並設置下一項的位置。在遇到yield return、yield break或迭代器塊結束時退出狀態
  3. Suspended:狀態機等待下次調用MoveNext的狀態
  4. After:沒有更多項能夠枚舉

若是狀態機在Before或Suspended狀態有一次MoveNext調用就進入Running狀態。在Running狀態中檢測集合的下一項並設置位置。若是有更多項狀態機會轉入Suspended狀態,若是沒有更多項則轉入並保持在After狀態,如圖所示:

3.總結

總而言之,實際上是對迭代器設計模式的運用簡化。既不暴露集合的內部結構,又可以讓外部代碼透明的訪問集合內部的數據,這是迭代器設計模式的思想。

相關文章
相關標籤/搜索