C# 9.0 功能預覽 (6) GetEnumerator extension

GetEnumerator 在 C# 9 開始,能以擴充方法的形式存在。

環境
Visual Studio 2019 - 16.8.0 Preview 3.2
.NET 5.0.0-rc.1.20451.14

以往在 C# 中如果一個型別要具備迭代器的功能必須要實作 GetEnumerator 方法, 而且這個方法必須是執行個體方法。 C# 9 對此做了一個改變,GetEnumerator 方法可以是擴充方法 (extension method)。

比方說我們為 int 建立一個 GetEnumerator<T>的 extension :

 public static class IntExtensions
 {
     public static IEnumerator<int> GetEnumerator(this int max)
     {
         return Enumerable.Range(0, max).GetEnumerator();
     }
 }

然後就可以很奇妙地這樣使用:

 foreach (int i in 10)
 {
     Console.WriteLine(i);
 }

這讓事情變得很有趣,比方我可以寫一個自己的集合 (只是個範例,內容並沒有太完整),這個集合不實作任何相關的介面:

   public class MyCollection<T> 
    {
        private T[] _data;

        private int _count;

        public int Capacity => _data.Length;

        public int Count => _count;

        public T this[int index]
        {
            get { return _data[index]; }
            set
            {
                if (index == _count || index < 0) throw new IndexOutOfRangeException();
                _data[index] = value;
            }
        }

        public MyCollection() : this(4)
        {

        }

        public MyCollection(int capacity)
        {
            _count = 0;
            CreateArray(capacity);
        }

        private void CreateArray(int capacity)
        {
            _data = new T[capacity];
        }

        public void Add(T obj)
        {
            if (_count == _data.Length)
            {

                var source = _data;
                CreateArray(_data.Length * 2);
                Array.Copy(source, _data, source.Length);
            }

            _data[_count] = obj;
            _count++;
        }
        
    }

理所當然這個自訂集合是沒法迭代的,因為沒有撰寫任何 GetEnumerator 的執行個體方法,接著就加上一個擴充方法:

 public static class MyCollectionExtension
 {
     public static IEnumerator<T> GetEnumerator<T>(this MyCollection<T> source)
     {
         for (int i = 0; i < source.Count; i++)
         {
             yield return source[i];
         }
     }
 }

這樣,MyCollection<T> 就能支援迭代了。

 static void Main(string[] args)
 {
     var collection = new MyCollection<string>();
     collection.Add("A");
     collection.Add("B");
     collection.Add("C");
     collection.Add("D");
     collection.Add("E");
     collection.Add("F");
     collection.Add("G");
     collection.Add("H");
     collection.Add("I");
     collection.Add("J");

     foreach (var item in collection)
     {
         Console.WriteLine(item);
     }

     Console.ReadLine();
 }

我們也可以拿 Range struct 來玩玩看:

 class Program
 {
     static void Main(string[] args)
     {
         foreach (var item in 2..11)
         {
             Console.WriteLine(item);
         }
     }
 }

 public static class RangeExtension
 {
     public static IEnumerator<int> GetEnumerator(this Range range)
     {
         for (int i = range.Start.Value; i < range.End.Value; i++)
         {
             yield return i;
         }
     }
 }