首先我们去core的源码中去找IEnumerable发现并没有,如下
Core中应该是直接使用.net中对IEnumerable的定义
自己实现迭代器
迭代器是通过IEnumerable和IEnumerator接口来实现的,今天我们也来尝试实现自己的迭代器。
首先来看看这两个接口:
internal interface IEnumerable { [DispId(-4)] System.Collections.IEnumerator GetEnumerator(); } public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); }
并没有想象的那么复杂。其中IEnumerable只有一个返回IEnumerator的GetEnumerator方法。而IEnumerator中有两个方法加一个属性。
接下来,我们继承IEnumerable接口并实现:
public class MyIEnumerable : IEnumerable { private string[] strList; public MyIEnumerable(string[] strList) { this.strList=strList; } public IEnumerator GetEnumerator() { return new MyIEnumerator(strList); } } public class MyIEnumerator:IEnumerator { private string[] strList; private int position; public MyIEnumerator(string[] strList) { this.strList=strList; position=-1; } public object Current { get{ return strList[position];} } public bool MoveNext() { position++; if (position<strList.Length) { return true; } return false; } public void Reset() { position=-1; } }
下面使用原始的方式调用:
static void Main(string[] args) { string[] strList=new string[]{"123","1231"}; MyIEnumerable my =new MyIEnumerable(strList); var enumerator=my.GetEnumerator(); while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); //enumerator.Current=""; 这会报错 } Console.WriteLine("-------------------------------"); foreach (var item in my) { Console.WriteLine(item); } }
这两种取值方式基本等效,因为实际clr编译后生成的代码是相同的。
由此可见,两者有这么个关系:
我们可以回答一个问题了“为什么在foreach中不能修改item的值?”:
我们还记得IEnumerator的定义吗,接口的定义就只有get没有set。所以我们在foreach中不能修改item的值。
yield的使用
你肯定发现了我们自己去实现IEnumerator接口还是有些许麻烦,并且上面的代码肯定是不够健壮。对的,.net给我们提供了更好的方式。
public IEnumerator GetEnumerator() { //return new MyIEnumerator(strList); for (int i = 0; i < strList.Length; i++) { yield return strList[i]; } }
你会发现我们连MyIEnumerator都没要了,也可以正常运行。太神奇了。yield到底为我们做了什么呢?
好家伙,我们之前写的那一大坨。你一个yield关键字就搞定了。最妙的是这块代码:
这就是所谓的状态机吧!
我们调用GetEnumerator的时候,看似里面for循环了一次,其实这个时候没有做任何操作。只有调用MoveNext的时候才会对应调用for循环。
为什么Linq to Object中要返回IEnumerable?
因为IEnumerable是延迟加载的,每次访问的时候才取值。也就是我们在Lambda里面写的where、select并没有循环遍历(只是在组装条件),只有在ToList或foreache的时候才真正去集合取值了。这样大大提高了性能。
自己实现MyWhere:
public class MyIEnumerable : IEnumerable { private string[] strList; public MyIEnumerable(string[] strList) { this.strList=strList; } public IEnumerator GetEnumerator() { //return new MyIEnumerator(strList); for (int i = 0; i < strList.Length; i++) { yield return strList[i]; } } public IEnumerable<string> MyWhere(Func<string,bool> func) { foreach (string item in this) { if (func(item)) { yield return item; } } } }
FirstOrDefault的实现
内部调用了TryGetFirst。
private static TSource TryGetFirst<TSource>(this IEnumerable<TSource> source, out bool found) { if (source == null) { throw Error.ArgumentNull(nameof(source)); } if (source is IPartition<TSource> partition) { return partition.TryGetFirst(out found); } if (source is IList<TSource> list) { if (list.Count > 0) { found = true; return list[0]; } } else { using (IEnumerator<TSource> e = source.GetEnumerator()) { //同样调用了MoveNext方法 if (e.MoveNext()) { found = true; //Current属性在我们的自定义实现里面也有出现 return e.Current; } } } found = false; return default(TSource); }
private static TSource TryGetFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out bool found) { if (source == null) { throw Error.ArgumentNull(nameof(source)); } if (predicate == null) { throw Error.ArgumentNull(nameof(predicate)); } if (source is OrderedEnumerable<TSource> ordered) { return ordered.TryGetFirst(predicate, out found); } foreach (TSource element in source) { //循环,直接返回第一个符合条件的对象 if (predicate(element)) { found = true; return element; } } found = false; return default(TSource); }
源码地址
https://gitee.com/qixinbo/MyKestrelServer/tree/master/DataStruct/EnumerableStudy
本文参考《农码一生》