IEnumerable实践应用
1.集合想要支持foreach方式遍历,需要返回一个迭代器(IEnumerator),foreach会自动调用迭代器的状态迁移(MoveNext()、Curent、Reset())
#region Assembly mscorlib.dll, v4.0.0.0 // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll #endregion using System; using System.Runtime.InteropServices; namespace System.Collections { // Summary: // Supports a simple iteration over a nongeneric collection. [ComVisible(true)] [Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")] public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); } }
2.IEnumerable接口定制了foreach的需求
#region Assembly mscorlib.dll, v4.0.0.0 // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll #endregion using System.Runtime.InteropServices; namespace System.Collections { // Summary: // Exposes the enumerator, which supports a simple iteration over a non-generic // collection. [ComVisible(true)] [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")] public interface IEnumerable { [DispId(-4)] IEnumerator GetEnumerator(); } }
3.类似于List的列表定义如下:
/// <summary> /// 列表 /// </summary> class MyList : ArrayList, IEnumerable<object> { // 迭代器返回 public IEnumerator<object> GetEnumerator() { return new MyListEnumerator(this); } /// <summary> /// 1.由于IEnumerable<T>继承自IEnumerable,因此必须实现其方法 /// 2.由于已经存在同名方法,因此需要显示实现 /// </summary> /// <returns></returns> IEnumerator IEnumerable.GetEnumerator() { //return GetEnumerator(); // IEnumerator是IEnumerator<T>的基类 return null; } } /// <summary> /// 为列表定制的迭代器 /// </summary> class MyListEnumerator : IEnumerator<object> { private int current = -1; private MyList _mylist; public MyListEnumerator(MyList mylist) { _mylist = mylist; current = _mylist == null || _mylist.Count == 0 ? -1 : 0; } public bool MoveNext() { current++; return _mylist != null && _mylist.Count > current; } object IEnumerator<object>.Current { get { if (_mylist != null && _mylist.Count > current) return _mylist[current]; return null; } } // 1.由于IEnumerable<out T>继承自IEnumerable,因此必须实现其方法 // 2.由于已经存在同名方法,因此需要显示实现 object IEnumerator.Current { get { //if (_mylist != null && _mylist.Count > current)return _mylist[current]; return null; } } public void Reset() { current = _mylist == null || _mylist.Count == 0 ? -1 : 0; } // IEnumerator<T>继承了IDisposable,为了遍历完后清理状态,所以需要实现该方法 // 该方法在foreach循环完毕后自动调用 public void Dispose() { } }
4.IEnumerator作为迭代器,源于底层编程语言指针的启发;
引用类型不同于值类型的一个重要特点就是用到的时候才会进行真正的处理;
.Net中yield关键字对此有很好的支持,以下举例说明:
static void Main(string[] args) { var list = GetStrings(5); foreach (var item in list) { Console.WriteLine(item); } list = GetStrings(10); foreach (var item in list) { Console.WriteLine(item); } Console.ReadLine(); }
运行结果:
/// <summary> /// 1.yield return将单次运算结果放入IEnumerable集合中 /// 2.yield break中断当前迭代器的作用域 /// </summary> /// <param name="length"></param> /// <returns></returns> public static IEnumerable<string> GetStrings(int length = 0) { for (int i = 0; i < length; i++) { if (i < 5) { yield return string.Format("第{0}次调用", i); } else { yield break; } } Console.WriteLine("yield break之后在迭代作用域内的代码不会再被执行"); }
补充:上面GetStrings传统上很多人习惯如下写法,但是这样做的时间开销是n*m,其中n为待运算列表长度,m为第一次遍历长度,这就是为什么上面强调用到的时候才会进行真正的处理
public static List<string> GetStrings(int length = 0) { var result = new List<string>(); for (int i = 0; i < length; i++) { if (i < 5) { result.Add(string.Format("第{0}次调用", i)); } } return result; }