剛開始學Linq的時候,學會使用Select就覺得很興奮。等某天看到SelectMany時,就覺得非常很神奇了,這什么東西,怎樣使用法啊。有時候,學習光靠看,是不能理解的,最好是看,理解和實踐操作。
本文借鑒了https://www.xuebuyuan.com/851846.html 的文章,補充部分注釋和實例。
首先看下面兩個類的定義:
class Student { public int Score { get; set; } public Student(int score) { this.Score = score; } } class Teacher { public string Name { get; set; } public List<Student> Students; public Teacher(string order,List<Student> students) { this.Name = order; this.Students = students; } }
用以上兩個類構建集合如下:
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) }) };
這里有7個老師,每個人有3個學生,總共21一個學生里又有3個倒霉蛋沒考及格……我們想要獲得這3個倒霉蛋的集合。C# 2.0的代碼如下:
List<Student> studentList = new List<Student>(); foreach (var t in teachers) { foreach (var s in t.Students) { if (s.Score < 60) { studentList.Add(s); } } }
已經寫了N多這樣的二重foreach,寫的都要吐了,簡直恨不得做成代碼段。
var list1 = from t in teachers from s in t.Students where s.Score < 60 select s;
是不是感覺好多了,就跟寫SQL一樣順暢。而且一目了然。也許習慣於OOXX的.NET程序員不那么喜歡SQL的語法,那還可以試試Lamda表達式的寫法,這就必須Select大顯身手的時候了。
如下官方解釋:
Projects each element of a sequence to an System.Collections.Generic.IEnumerable<T>,
// flattens the resulting sequences into one sequence, and invokes a result
// selector function on each element therein.
SelectMany 可以把一個集合中的元素投影到IEnumerable<T>類型的集合中去,然后再合並結果並集到一個集合中.
SelectMany把Teacher中的Student集合投注到IEnumeralbe<Student>集合中去,注意要想使用SelectMany,對象必須嵌套一個集合類型的對象才可以.所以說SelectMany專門用來替換二層循環也很適當 .
可以很方便的處理二重循環問題.
var list2 = teachers.SelectMany(t => t.Students).Where(s => s.Score < 60);
把鼠標放在SelectMany上面會顯示如下注釋,首先SelecMany會對每一個Teacher執行Func<Teacher,IEnumerable<Student>>委托(Func<Teacher,IEnumerable<Student>>委托是一個用Teacher做為參數並返回一個IEnumerable<Student>集合的委托類型.).接下平合並所有IEnumerable<Student>集合的功能交由SelectMandy內部來完成.它會把
把有的IEnumerable<Student>合並成一個大的集合.
public static IEnumerable<Student> SelectMany<Teacher,Student>( this IEnumerable<Teacher> source, Func<Teacher, IEnumerable<Student>> selector
原型如下:
public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector )
這個用於IEnumerable<T>的擴展方法接受一個Func委托,根據你的需要再返回另一個IEnumerable<T>,配合Where真是秒殺二重foreach啊。
再舉個例子,把所有學生的成績按照從高到底排序出來;
var scores = teachers.SelectMany((t, i) => t.Students.Select(s => s.Score)).OrderByDescending(t => t).ToList();
scores.ForEach(s => Console.WriteLine(s));
有時候我們需要輸出更復雜的結果集,比如校長想知道教出這3個考不及格的倒霉蛋的,到底是哪幾個更加倒霉的老師。那我們就要用到SelectMany的另一個重載方法了:
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector )
第一眼看上去有點暈,重點是第一個Func委托的返回值IEnumerable<TCollection>,會遍歷作為第二個Func委托的參數TCollection傳遞,供你構建所需要的投影集合。這里給出一個例子,下面的代碼選出了門下有不及格學生的倒霉蛋老師+門生的分數:
var list3 = teachers.SelectMany( t => t.Students, (t, s) => new { t.Name, s.Score }) .Where(n => n.Score < 60);
在這里,校長大人得到的集合,不僅包含了所有不及格的分數,同時還對應了該分數學生的教師姓名。