1. 程式人生 > 實用技巧 >C# foreach 底層原理分析及自定義 MyList

C# foreach 底層原理分析及自定義 MyList

foreach 在我們進行.net 開發時算是比較常見的,但是foreach 內部呼叫流程是啥,需要滿足什麼條件才能使用foreach。

我們使用List為例子,走一下原始碼。

//List定義定義 
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
//4種介面定義
IEnumerable
    IEnumerator GetEnumerator();    
IEnumerator
    
bool MoveNext(); object Current; void Reset(); IEnumerable<out T> : IEnumerable IEnumerator<T> GetEnumerator(); IEnumerator<out T> : IDisposable, IEnumerator T Current;

0- 新建控制檯程式:

    static void Main(string[] args)
    {

        List<int> test = new List<int
> { 1, 2, 3, 4}; foreach (var item in test) { Console.WriteLine(); } Console.ReadKey(); }

1- List進行了初始化,將一個空陣列賦值給了_items陣列

2- 然後List進行初始化的時候呼叫了Add方法,然後進行了擴容判斷 (具體如何擴容就不貼出來了)

3- 執行foreach的時候,呼叫了GetEnumerator() ,獲得了一個迭代器。注意看這裡的返回值是一個List<T>.Enumerator,它在List內部被定義為了一個結構體。

3-1 遍歷資料的時候 第一步執行 迭代器的 MoveNext方法,

3-2 獲取迭代器 Current 屬性值

這裡我們得出一個結論,只要一個類實現了IEnumerable 介面能夠返回一個迭代器,那就可以使用foreach進行遍歷。

舉一反三,我們理解了foreach原理後,模仿List 建立一個我們自己的MyList,然後可以傳遞Func委託過濾遍歷的類(玩一玩,運用一下)

    public class MyList<T> : IEnumerable
    {
        private static readonly T[] _emptyArray = new T[0];
        private const int _defaultCapacity = 4;
        private T[] _items;
        private int _size;
        private int _version;
        private Func<T, bool> _filterFunc;

        public int Count => _size;
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public MyList()
        {
            this._items = _emptyArray;
        }

        /// <param name="func"></param>
        public MyList(Func<T, bool> func)
        {
            this._items = _emptyArray;
            _filterFunc = func;
        }

        public IEnumerator GetEnumerator()
        {
            return new Enumerator(this);
        }

        public void Add(T item)
        {
            if (this._size == this._items.Length)
                this.EnsureCapacity(this._size + 1);
            this._items[this._size++] = item;
            ++this._version;
        }

        /// <summary>
        /// 擴容機制
        /// </summary>
        /// <param name="min"></param>
        private void EnsureCapacity(int min)
        {
            if (this._items.Length >= min)
                return;
            int num = this._items.Length == 0 ? 4 : this._items.Length * 2;
            if ((uint)num > 2146435071U)
                num = 2146435071;
            if (num < min)
                num = min;
            this.Capacity = num;
        }

        public int Capacity
        {
            get => this._items.Length;

            set //有點子監聽的感覺
            {
                if (value == this._items.Length)
                    return;
                if (value > 0)
                {
                    T[] objArray = new T[value];
                    if (this._size > 0) //擴容真正實現在這裡
                        Array.Copy((Array)this._items, 0, (Array)objArray, 0, this._size);
                    this._items = objArray;
                }
                else
                    this._items = _emptyArray;
            }
        }

        /// <summary>
        /// 迭代器定義
        /// </summary>
        public struct Enumerator : IEnumerator
        {
            private MyList<T> _list;
            private int _index;
            private int _version;
            private T current;

            internal Enumerator(MyList<T> list)
            {
                this._list = list;
                this._index = 0;
                this._version = list._version;
                this.current = default(T);
            }

            public bool MoveNext()
            {
                if (this._version != _list._version || (uint)this._index >= (uint)_list._size)
                    return this.MoveNextRare();

                this.current = _list._items[this._index];
                ++this._index;

                //當不滿足過濾條件時,foreach遍歷Current返回為空
                if (_list._filterFunc!=null && !_list._filterFunc(this.current))
                    this.current = default(T);

                return true;
            }

            private bool MoveNextRare()
            {
                this._index = this._list._size + 1;
                this.current = default(T);
                return false;
            }

            public object Current => this.current;

            public void Reset()
            {
                _index = 0;
                current = default(T);
            }
        }
    }

控制檯測試程式:

    class Program
    {
        static void Main(string[] args)
        {
            MyList<int> list1 = new MyList<int>(new Func<int, bool>(m => m > 5)) {1, 6, 3, 9, 10, 2, 4};
            foreach (var item in list1)
            {
                if (item != null)
                    Console.WriteLine(item);
            }

            Console.ReadKey();
        }
    }

輸出結果:(初始化MyList時 我們用了int?, 因為default<int> 預設值為0,對於後面的null判空不生效)

最後補充一點,迭代器 MoveNext()方法返回值false時,會直接終止遍歷。