C# foreach 底層原理分析及自定義 MyList
阿新 • • 發佈:2020-11-18
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(); IEnumeratorbool 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時,會直接終止遍歷。