在上一篇中簡單介紹了Linq的入門級用法,這一篇嘗試講解一些更加深刻的使用方法,與前一篇的結構不同的地方是,這一篇我會先介紹Linq裏的支持方法,而後以實際需求爲引導,分別以方法鏈的形式和類SQL的形式寫出來。html
Predicate<T>
謂詞、斷言,等價於 Func<T,bool>
即返回bool的表達式Expression<TDelegate>
表達式樹,這個類很關鍵,可是在這裏會細說,咱們會講它的一個特殊的泛型類型:Expression<Func<T,bool>>
這個在某些數據源的查詢中十分重要,它表明lambda表達式中一種特殊的表達式,即沒有大括號和return
關鍵字的那種。咱們先準備兩個類:java
/// <summary> /// 學生 /// </summary> public class Student { /// <summary> /// 學號 /// </summary> public int StudentId { get; set; } /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 班級 /// </summary> public string Class { get; set; } /// <summary> /// 年齡 /// </summary> public int Age { get; set; } }
Subject/科目類:數據庫
/// <summary> /// 科目 /// </summary> public class Subject { /// <summary> /// 名稱 /// </summary> public string Name { get; set; } /// <summary> /// 年級 /// </summary> public string Grade { get; set; } /// <summary> /// 學號 /// </summary> public int StudentId { get; set; } /// <summary> /// 成績 /// </summary> public int Score { get; set; } }
Subject 和Student經過學號字段一一關聯,實際工做中數據表有可能會設計成這。c#
那麼先虛擬兩個數據源:IEnumerable<Student> students
和 IEnumerable<Subject> subjects
。先忽略這兩個數據源的實際來源,由於在開發過程當中數據來源有不少種狀況,有數據庫查詢出來的結果、遠程接口返回的結果、文件讀取的結果等等。不過最後都會整理成IEnumerable<T>
的子接口或實現類的對象。api
where的方法聲明:app
public IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate)
能夠看出不會轉換數據類型,經過給定的lambda表達式或者一個方法進行過濾,獲取返回true的元素。異步
示例:函數
// 獲取年紀大於10但不大於12的同窗們 List<Student> results = students.Where(t=>t.Age >10 && t.Age<= 12).ToList();
注意在調用ToList以後數據纔會實質上查詢出來。this
Group的方法聲明有不少種:spa
最經常使用的一種是:
public static IEnumerable<System.Linq.IGrouping<TKey,TSource>> GroupBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector);
示例:
//將學生按照班級進行分組 List<IGrouping<string,Student>> list = students.GroupBy(p => p.Class).ToList();
它們是一對方法,一個是升序一個降序,其聲明是同樣的:
經常使用的是:
public static System.Linq.IOrderedEnumerable<TSource> OrderBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector);
示例:
//按年齡的升序排列: List<Student> results = students.OrderBy(p => p.Age).ToList(); //按年齡的降序排列: List<Student> results = students.OrderByDescending(p => p.Age).ToList();
這組方法有兩個經常使用的重載聲明:
First:
// 直接獲取第一個 public static TSource First<TSource> (this IEnumerable<TSource> source); // 獲取知足條件的第一個 public static TSource First<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);
Last:
// 直接獲取最後一個 public static TSource Last<TSource> (this IEnumerable<TSource> source); // 獲取最後一個知足條件的元素 public static TSource Last<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);
示例:
Student student = students.First();// 等價於 students[0]; Student student = students.First(p=>p.Class == "一班");//獲取數據源中第一個一班的同窗 Student student = students.Last();//最後一個學生 Student student = students.Last(p=>p.Class == "三班");//獲取數據源中最後一個三班的同窗
注意:
Any:是否存在元素知足條件
有兩個版本,不過意思可能不太同樣:
public static bool Any<TSource> (this IEnumerable<TSource> source);//數據源中是否有數據 //================ //是否存在知足條件的數據 public static bool Any<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);
All :是否都知足條件:
public static bool Any<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);
示例:
// 是否有學生 bool isAny = students.Any(); // 是否有五班的同窗 bool isFive = students.Any(p=>p.Class == "五班"); // 是否全部學生的年紀都不小於9歲 bool isAll = students.All(p=>p.Age >= 9);
Skip一共有三個衍生方法:
第一個:Skip 本身: 略過幾個元素,返回剩下的元素內容
public static IEnumerable<TSource> Skip<TSource> (this IEnumerable<TSource> source, int count);
第二個:SkipLast,從尾巴開始略過幾個元素,返回剩下的元素內容
public static IEnumerable<TSource> SkipLast<TSource> (this IEnumerable<TSource> source, int count);
第三個:SkipWhile,跳過知足條件的元素,返回剩下的元素
public static IEnumerable<TSource> SkipWhile<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);
示例:
// 不保留前10個學生 List<Student> results = students.Skip(10).ToList(); // 不保留後10個學生 List<Student> results = students.SkipLast(10).ToList(); // 只要非一班的學生 List<Student> results = students.SkipWhere(p=>p.Class=="一班").ToList(); //上一行代碼 等價於 = students.Where(p=>p.Class != "一班").ToList();
Take與Skip同樣也有三個衍生方法,聲明的參數類型也同樣,這裏就不對聲明作介紹了,直接上示例。
//選取前10名同窗 List<Student> results = students.Take(10).ToList(); // 選取最後10名同窗 List<Student> results = students.TakeLast(10).ToList(); //選取 一班的學生 List<Student> results = students.TakeWhile(p=>p.Class=="一班").ToList(); // 上一行 等價於 = students.Where(p=>p.Class=="一班").ToList();
在使用Linq寫分頁的時候,就是聯合使用Take和Skip這兩個方法:
int pageSize = 10;//每頁10條數據 int pageIndex = 1;//當前第一頁 List<Student> results = students.Skip((pageIndex-1)*pageSize).Take(pageSize).ToList();
其中 pageIndex能夠是任意大於0 的數字。Take和Skip比較有意思的地方就是,若是傳入的數字比數據源的數據量大,根本不會爆粗,只會返回一個空數據源列表。
官方對於Select的解釋是,將序列中的每一個元素投影到新的表單裏。個人理解就是,本身 定義一個數據源單個對象的轉換器,而後按照本身的方式對數據進行處理,選擇出一部分字段,轉換一部分字段。
因此按個人理解,我沒找到java8的同效果方法。(實際上java用的是map,因此沒找到,:-D)
public static System.Collections.Generic.IEnumerable<TResult> Select<TSource,TResult> (this IEnumerable<TSource> source, Func<TSource,TResult> selector);
示例:
// 選出班級和姓名 List<object> results = students.Select(p => new { p.Class, p.Name }).ToList();
Linq 裏有幾個須要注意的簡單運算操做,這部分在使用中很常見。
Max獲取數據源中最大的一個,不過只能是數字類型的,其餘類型由於不能直接比較大小因此能夠有替代方法,就是先排序取第一個。
如下是Max方法的兩個重載版本:
public static int Max (this IEnumerable<int> source); public static int Max <TSource>(this IEnumerable<TSource> source,Func<TSource,int> selector);
示例:
//查詢學生中最大的年紀是多少 int maxAge = students.Select(t=>t.Age).Max();
方法相似與Max,不過與之不一樣的是獲取最小的一個,不能應用於非數字類型。
示例:
// 查詢學生中最小的年紀是多少 int minAge = students.Select(t=> t.Age).Min(); //======= int minAge = students.Min(p=>p.Age);
與 Max/Min是同樣類型的方法,依舊不能應用於非數字類型。
示例:
// 查詢學生的評價年紀 int averageAge = students.Select(t=>t.Age).Average(); int averageAge = students.Average(p=>p.Age);
對數據源進行求和或者對數據源的某個字段進行求和,仍是不能對非數字類型進行求和
示例:
// 一個沒有實際意義的求和,學生的年齡總和 int sumAge = students.Select(t=>t.Age).Sum(); // int sumAge = students.Sum(p=>p.Age);
判斷數據源中是否包含某個元素,返回一個bool值,若是包含則返回true,若是不包含則返回false。該方法有兩個重載版本,一個是使用默認的Equals
方法,一個是指定一個相等性比較器實現類。
public static bool Contains<TSource> (this IEnumerable<TSource> source, TSource value); //傳入相等性比較器的 public static bool Contains<TSource> (this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer);
值得注意的是,這裏的相等比較器是一個接口,也就是說須要使用類來實現這個方法。一般在實際開發過程當中,咱們會在TSource這個數據源所表明的類上增長 IEqualityCompare的實現。
示例1:
Student student1 = new Student();// 初始化一個學生類 Student student2 = students.First();// 從數據源中取一個 bool isContains = students.Contains(student1);// 返回 false, bool isContains2 = students.Contains(student2);// 返回 true
說明: 類的默認相等比較是比較是不是同一個對象,即返回的
示例2:
建立一個相等性比較器,值得注意的是,相等性比較器有兩個方法,一個是比較元素是否相等,一個是返回元素的HashCode,這兩個方法必須在判斷元素是否相等上保持結果一致。
public class StudentEqualityCompare: IEqualityComparer<Student> { public bool Equals(Student x, Student y) { // 省略邏輯 } public int GetHashCode(Student obj) { //省略邏輯 } }
使用:
StudentEqualityCompare compare = new StudentEqualityCompare(); Student student = students.First(); bool isContains = students.Contains(student, compare);
這是一組行爲同樣的方法,就是對數據源進行計數,不一樣的是Count返回int,LongCount返回long。
它們的聲明有如下兩種,這裏選了Count的聲明:
public static int Count<TSource> (this IEnumerable<TSource> source); public static int Count<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);
示例:
int count = students.Count();//返回一共有多少個學生 int count = students.Count(p=>p.Class=="一班");// 統計一班一共有多少學生
以前介紹了單個數據源的操做方法,這些方法不會讓數據源發生變化,更多的對數據源進行過濾和選擇或者統計。如今介紹幾個對多個數據源進行操做的方法。
聯合另外一個數據源,意思就是把兩個數據源合併到一個裏面,去掉重複的元素,只保留不重複的元素,並返回這個結果集。
與Contains方法差很少,這個方法有兩個重載的版本:
public static IEnumerable<TSource> Union<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second); public static IEnumerable<TSource> Union<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
示例:
先假設一個業務場景:
學校舉辦運動會,如今教務處收到了田徑組 500米跑的報名名單和跳遠的報名名單,須要看看一共有哪些學生報名了這兩項賽事。
// 省略數據源,田徑組的名單 IEnumerable<Student> students1 = new List<Student>(); //省略數據源來源,跳遠組的名單 IEnumerable<Student> students2 = new List<Student>(); List<Student> all = students1.Union(student2).ToList();
這時候簡單統計了一下全部人,可是後來教務處在覈對的時候,發現有的人名重複了,須要判斷是不是一我的,這時候就必須建立一個相等比較器了。
List<Student> all = students1.Union(student2,compare).ToList(); // 省略compare的實現,具體可參照Contains的比較器
獲取同時存在於兩個集合中的元素,與Union相似。
方法的聲明以下:
public static IEnumerable<TSource> Intersect<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second); public static IEnumerable<TSource> Intersect<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
示例:
繼續以前的業務場景,如今教務處須要知道有哪些同窗同時報名了兩個比賽
List<Student> students = students1.Intersect(students2).ToList();
獲取只存在於第一個集合的元素,從第一個集合中去除同時存在與第二個集合的元素,並返回。
方法的聲明以下:
public static IEnumerable<TSource> Except<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second); public static IEnumerable<TSource> Except<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
示例:
繼續業務描述,教務處要一份只報名了500米的學生名單:
List<Student> students = students1.Except(students2).ToList();
數據源中的元素本來有必定的順序,這個方法能夠將數據源中的順序翻轉過來,本來是最後一個的變成了第一個
,第一個變成了最後一個。
簡單示例:
char[] apple = { 'a', 'p', 'p', 'l', 'e' }; char[] reversed = apple.Reverse().ToArray();
對數據源進行去重,而後返回去重以後的結果。一樣,這個方法有兩個重載版本,一個有比較器,一個沒有比較器。
// 不用比較器的 public static IEnumerable<TSource> Distinct<TSource> (this IEnumerable<TSource> source); // 設置比較器 public static IEnumerable<TSource> Distinct<TSource> (this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);
示例:
先描述一個可能會出現的場景,每一個班級在各個賽事組提交報名信息的時候有點混亂,500米的負責老師把一個班的名單多錄了一次,可是學生已經亂序了,如今須要把多錄的去掉,也就是對數據進行去重。
List<Student> students = students1.Distinct();
以前的方法基本都是對一個類型的數據源進行操做,不會涉及其餘類型的數據源。如今介紹一下怎麼關聯多個類型的數據源,相似於SQL裏的多表連接查詢。
按照必定的邏輯將兩個數據源關聯到一塊兒,而後選擇出須要的數據。
方法有這幾個重載版本:
public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector); // public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector, IEqualityComparer<TKey> comparer);
這個方法的參數比較多,咱們大概介紹一下這個方法的全部參數:
類型參數
TOuter 第一個序列中的元素的類型。
TInner 第二個序列中的元素的類型。
TKey 選擇器函數返回的鍵的類型。
TResult 結果元素的類型。
參數
outer IEnumerable<TOuter> 要聯接的第一個序列。
inner IEnumerable<TInner> 要與第一個序列聯接的序列。
outerKeySelector Func<TOuter,TKey> 用於從第一個序列的每一個元素提取聯接鍵的函數。
innerKeySelector Func<TInner,TKey> 用於從第二個序列的每一個元素提取聯接鍵的函數。
resultSelector Func<TOuter,TInner,TResult> 用於從兩個匹配元素建立結果元素的函數。
comparerIEqualityComparer<TKey> 用於對鍵進行哈希處理和比較的 IEqualityComparer。
示例:
假設前天語文老師組織了一場考試,由於是模擬正式考試,因此答題紙上學生都只寫了學號,如今須要把考試成績和學生們聯繫在一塊兒
List<object> results = students.Join(subjects, p => p.StudentId, s => s.StudentId, (p, s) => new { Student = p, Subject = s }).ToList(); /** 返回一個學生和科目的匿名對象,不過被我用object接了,這裏會有一個問題,若是有興致能夠提早了解一下C#的var關鍵字和匿名對象,這部分將會放在C#基礎系列補全篇講解 */
基於鍵值等同性將兩個序列的元素進行關聯,並對結果進行分組。以上是官方介紹,我在開發過程當中並無使用過這個方法,不過這個方法徹底能夠認爲是Join和Group的組合體,即先進行了一次Join而後又對數據進行一次分組。
方法聲明:
// 使用默認比較器 public static IEnumerable<TResult> GroupJoin<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,IEnumerable<TInner>,TResult> resultSelector); //設置比較器 public static IEnumerable<TResult> GroupJoin<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,IEnumerable<TInner>,TResult> resultSelector, IEqualityComparer<TKey> comparer);
類型參數
TOuter 第一個序列中的元素的類型。
TInner 第二個序列中的元素的類型。
TKey 鍵選擇器函數返回的鍵的類型。
TResult 結果元素的類型。
參數
outer IEnumerable<TOuter> 要聯接的第一個序列。
inner IEnumerable<TInner> 要與第一個序列聯接的序列。
outerKeySelector Func<TOuter,TKey> 用於從第一個序列的每一個元素提取聯接鍵的函數。
innerKeySelector Func<TInner,TKey> 用於從第二個序列的每一個元素提取聯接鍵的函數。
resultSelector Func<TOuter,IEnumerable<TInner>,TResult> 用於從第一個序列的元素和第二個序列的匹配元素集合中建立結果元素的函數。
comparer IEqualityComparer<TKey> 用於對鍵進行哈希處理和比較的 IEqualityComparer。
如下是官方給的示例:
class Person { public string Name { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } public static void GroupJoinEx1() { Person magnus = new Person { Name = "Hedlund, Magnus" }; Person terry = new Person { Name = "Adams, Terry" }; Person charlotte = new Person { Name = "Weiss, Charlotte" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; List<Person> people = new List<Person> { magnus, terry, charlotte }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy }; // Create a list where each element is an anonymous // type that contains a person's name and // a collection of names of the pets they own. var query = people.GroupJoin(pets, person => person, pet => pet.Owner, (person, petCollection) => new { OwnerName = person.Name, Pets = petCollection.Select(pet => pet.Name) }); foreach (var obj in query) { // Output the owner's name. Console.WriteLine("{0}:", obj.OwnerName); // Output each of the owner's pet's names. foreach (string pet in obj.Pets) { Console.WriteLine(" {0}", pet); } } } /* This code produces the following output: Hedlund, Magnus: Daisy Adams, Terry: Barley Boots Weiss, Charlotte: Whiskers */
以上是關於Linq的全部方法內容,可是這仍然不是Linq的所有。後續還會有一篇關於Linq的另外一種查詢方式的內容文章。
更多內容煩請關注個人博客
原文出處:https://www.cnblogs.com/c7jie/p/12632800.html