天天看點

[轉]Linq使用心得——SelectMany替代二重foreach循環

本篇記錄了Linq學習的心得,較為淺顯,各位大牛請輕拍。

        學習Linq其實已經很久了,但是一直沒有使用的習慣,故水準也始終沒有提高。近來刻意強迫自己用Linq來替代C# 2.0的一些寫法。這裡有一些心得和各位分享一下。

        首先看下面兩個類的定義:

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,寫的都要吐了,簡直恨不得做成代碼段。因為所有程式設計語言都能這麼寫,有人覺得C#簡單,可好學了,抓到就寫,民工專用,抄襲Java,微軟出品,必屬垃圾,明天正午12點之前就會被淘汰……

        反正我覺得C# 3.0之後是越來越難,越來越複雜。難道是年紀大了智商堪憂……那既然要反駁,今天我們就換個C# 3.0的新寫法。首先是查詢表達式的寫法:

var list1 = from t in teachers
                        from s in t.Students where s.Score < 60 select s;      

        是不是感覺好多了,就跟寫SQL一樣順暢。而且一目了然。也許習慣于OOXX的.NET程式員不那麼喜歡SQL的文法,那還可以試試Lamda表達式的寫法:

  var list2 = teachers.SelectMany(t => t.Students).Where(s => s.Score < 60);      

        嗯?怎麼隻有一行,這是不是太欺負人了,到時候公司數代碼行數算工錢的時候怎麼辦……嗯……這種公司你還是離了吧……

        寫到這裡我不禁感慨起SelectMany的偉大了,太好用了。其實我們剛才隻是用了最簡單的SelectMany也就是這個方法:

public static IEnumerable<TResult> SelectMany<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TResult>> selector )      

        這個用于IEnumerable<T>的擴充方法接受一個Func委托,根據你的需要再傳回另一個IEnumerable<T>,配合Where真是秒殺二重foreach啊。

        有時候我們需要輸出更複雜的結果集,比如校長想知道教出這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);      

        在這裡,校長大人得到的集合,不僅包含了所有不及格的分數,同時還對應了該分數學生的教師姓名。慘啊……

        那麼評論中有同學提到了不光要通過Linq查詢出來結果,還要進行批量更新。我當時看了也蒙了啊,因為Linq寫得也不熟。後來仔細一想其實是可以曲線救 國的。Linq是語言內建查詢的意思,他的查詢結果并不會影響原始的資料源。但是這并不能阻止你Select一個新的投影。

        由于不及格的倒黴蛋太多,校長決定給每個倒黴蛋再加10分:

var list4 = teachers.SelectMany(t => t.Students).Where(s => s.Score < 60).Select(n => new Student(n.Score + 10));      

        那麼這會不及格的就隻剩2個人了。還有的同學說我就是不想寫foreach,就是要寫成lamada表達式,其實還是有整容成lamda的ForEach 的,這裡為了慶祝一下評論數超過2位數,給所有學生永久加10分,這回是真改原始資料了,不過還是用到了ForEach,哈哈:

  teachers.SelectMany(t => t.Students).ToList().ForEach(s => { s.Score = s.Score + 10; Console.WriteLine(s.Score); });      

轉載于:https://www.cnblogs.com/xishuqingchun/p/5044133.html