上篇《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,說實話我沒有搞清楚為什么這里需要加。還望各位給我指點迷津。
最后感謝韋恩卑鄙,沒有這個胖胖就沒有這篇文章。哈哈。