上篇《Linq使用心得——SelectMany替代二重foreach循环》中我们学习了SelectMany的一些用法。不小心给韦恩卑鄙这个家伙看到了,他就唆使我写如何伪造一个SelectMany方法。这真是赶鸭子上架啊,所以今天我们就来试试看吧。其实也没啥好说的,直接上代码吧。
static class FakeLinq { public static IEnumerable<TResult> FakeSelectMany<TSource, TResult>( this IEnumerable<TSource> source,Func<TSource, IEnumerable<TResult>> selector) { foreach (var s in source) { foreach (var r in selector(s)) { yield return r; } } } public static IEnumerable<TResult> FakeSelectMany<TSource, TResult>( this IEnumerable<TSource> source,Func<TSource, int, IEnumerable<TResult>> selector) { int index = 0; foreach (var s in source) { foreach (var r in selector(s,index++)) { yield return r; } } } public static IEnumerable<TResult> FakeSelectMany<TSource, TCollection, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector) { foreach (var s in source) { foreach (var c in collectionSelector(s)) { yield return resultSelector(s, c); } } } public static IEnumerable<TResult> FakeSelectMany<TSource, TCollection, TResult>( this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector) { int index = 0; foreach (var s in source) { foreach (var c in collectionSelector(s,index++)) { yield return resultSelector(s, c); } } } }
我们来试试效果,发现用起来是完全一样的感觉:
List<Teacher> teachers = new List<Teacher> { new Teacher("a",new List<Student>{ new Student(100),new Student(90),new Student(30) }), new Teacher("b",new List<Student>{ new Student(100),new Student(90),new Student(60) }), new Teacher("c",new List<Student>{ new Student(100),new Student(90),new Student(40) }), new Teacher("d",new List<Student>{ new Student(100),new Student(90),new Student(60) }), new Teacher("e",new List<Student>{ new Student(100),new Student(90),new Student(50) }), new Teacher("f",new List<Student>{ new Student(100),new Student(90),new Student(60) }), new Teacher("g",new List<Student>{ new Student(100),new Student(90),new Student(60) }) }; var list1 = teachers.SelectMany(t => t.Students).Where(s => s.Score < 60).ToList(); var list2 = teachers.FakeSelectMany(t => t.Students).Where(s => s.Score < 60).ToList(); var list3 = teachers.SelectMany( t => t.Students, (t, s) => new { t.Name, s.Score }) .Where(n => n.Score < 60).ToList(); var list4 = teachers.FakeSelectMany( t => t.Students, (t, s) => new { t.Name, s.Score }) .Where(n => n.Score < 60).ToList();
是不是有种微软就是个骗子,原来这么简单的感觉?其实也没那么简单,在完成上述代码之后,我又用ILSpy去看了微软SelectMany的实现,发现主要有以下3点区别:
1.我没有对传入的参数做任何的有效性检测,这在平时做应用开发时可能不是大问题,但如果是写一个通用类库供他人使用是绝对不能缺少的:
if (source == null) { throw Error.ArgumentNull("source"); } if (selector == null) { throw Error.ArgumentNull("selector"); }
2.存在第二个int index参数的情况下,我们没有对index进行溢出的检测:
int num = -1; checked { foreach (TSource current in source) { num++; foreach (TResult current2 in selector(current, num)) { yield return current2; } } yield break; }
3.第三点就是上面的这个yield break,说实话我没有搞清楚为什么这里需要加。还望各位给我指点迷津。
最后感谢韦恩卑鄙,没有这个胖胖就没有这篇文章。哈哈。