前言
在上一篇中簡單介紹了Linq的入門級用法,這一篇嘗試講解一些更加深入的使用方法,與前一篇的結構不一樣的地方是,這一篇我會先介紹Linq里的支持方法,然后以實際需求為引導,分別以方法鏈的形式和類SQL的形式寫出來,
前置概念介紹
Predicate<T>謂詞、斷言,等價于Func<T,bool>即回傳bool的運算式Expression<TDelegate>運算式樹,這個類很關鍵,但是在這里會細說,我們會講它的一個特殊的泛型型別:Expression<Func<T,bool>>這個在某些資料源的查詢中十分重要,它代表lambda運算式中一種特殊的運算式,即沒有大括號和return關鍵字的那種,
我們先準備兩個類:
- Student/學生類:
/// <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通過學號欄位一一關聯,實際作業中資料表有可能會設計成這,
那么先虛擬兩個資料源:IEnumerable<Student> students 和 IEnumerable<Subject> subjects,先忽略這兩個資料源的實際來源,因為在開發程序中資料來源有很多種情況,有資料庫查詢出來的結果、遠程介面回傳的結果、檔案讀取的結果等等,不過最后都會整理成IEnumerable<T>的子介面或實作類的物件,
常見方法介紹
Where 過濾資料,查詢出符合條件的結果
where的方法宣告:
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之后資料才會實質上查詢出來,
Group 分組,依照指定內容進行分組
Group的方法宣告有很多種:
最常用的一種是:
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();
OrderBy/OrderByDescending 進行排序,按條件升序/降序
它們是一對方法,一個是升序一個降序,其宣告是一樣的:
常用的是:
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/Last 獲取資料源的第一個/最后一個
這組方法有兩個常用的多載宣告:
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 == "三班");//獲取資料源中最后一個三班的同學
注意:
- 在某些資料源中使用Last會報錯,因為對于一些管道型別的資料源或者說異步資料源,程式無法確認最后一個元素的位置,所以會報錯,解決方案:先使用OrderBy對資料源進行一次排序,使結果與原有順序相反,然后使用First獲取
- 當資料源為空,或者不存在滿足條件的元素時,呼叫這組方法會報錯,解決方案:呼叫FirstOrDefault/LastOrDefault,這兩組方法在無法查詢到結果時會回傳一個默認值,
Any/All 是否存在/是否都滿足
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一共有三個衍生方法:
第一個: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 選取幾個元素
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 選取
官方對于Select的解釋是,將序列中的每個元素投影到新的表單里,我的理解就是,自己 定義一個資料源單個物件的轉換器,然后按照自己的方式對資料進行處理,選擇出一部分欄位,轉換一部分欄位,
所以按我的理解,我沒找到java8的同效果方法,(實際上java用的是map,所以沒找到,??)
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獲取資料源中最大的一個,不過只能是數字型別的,其他型別因為不能直接比較大小所以可以有替代方法,就是先排序取第一個,
以下是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();
Min 選取最小的一個
方法類似與Max,不過與之不同的是獲取最小的一個,不能應用于非數字型別,
示例:
// 查詢學生中最小的年紀是多少
int minAge = students.Select(t=> t.Age).Min();
//=======
int minAge = students.Min(p=>p.Age);
Average 求平均數
與 Max/Min是一樣型別的方法,依舊不能應用于非數字型別,
示例:
// 查詢學生的評價年紀
int averageAge = students.Select(t=>t.Age).Average();
int averageAge = students.Average(p=>p.Age);
Sum 求和
對資料源進行求和或者對資料源的某個欄位進行求和,還是不能對非數字型別進行求和
示例:
// 一個沒有實際意義的求和,學生的年齡總和
int sumAge = students.Select(t=>t.Age).Sum();
//
int sumAge = students.Sum(p=>p.Age);
Contains 是否包含某個元素
判斷資料源中是否包含某個元素,回傳一個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/LongCount 數量查詢
這是一組行為一樣的方法,就是對資料源進行計數,不同的是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=="一班");// 統計一班一共有多少學生
同型別資料源的操作
之前介紹了單個資料源的操作方法,這些方法不會讓資料源發生變化,更多的對資料源進行過濾和選擇或者統計,現在介紹幾個對多個資料源進行操作的方法,
Union 聯合另一個同型別的資料源
聯合另一個資料源,意思就是把兩個資料源合并到一個里面,去掉重復的元素,只保留不重復的元素,并回傳這個結果集,
與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的比較器
Intersect 獲取兩個集合中都存在的資料
獲取同時存在于兩個集合中的元素,與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();
Except 獲取只在第一個資料源中存在的資料
獲取只存在于第一個集合的元素,從第一個集合中去除同時存在與第二個集合的元素,并回傳,
方法的宣告如下:
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();
Reverse 翻轉順序
資料源中的元素原本有一定的順序,這個方法可以將資料源中的順序翻轉過來,原本是最后一個的變成了第一個
,第一個變成了最后一個,
簡單示例:
char[] apple = { 'a', 'p', 'p', 'l', 'e' };
char[] reversed = apple.Reverse().ToArray();
Distinct 去重
對資料源進行去重,然后回傳去重之后的結果,同樣,這個方法有兩個多載版本,一個有比較器,一個沒有比較器,
// 不用比較器的
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里的多表鏈接查詢,
Join 關聯兩個資料源
按照一定的邏輯將兩個資料源關聯到一起,然后選擇出需要的資料,
方法有這幾個多載版本:
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
要聯接的第一個序列, -
inner IEnumerable
要與第一個序列聯接的序列, -
outerKeySelector Func<TOuter,TKey> 用于從第一個序列的每個元素提取聯接鍵的函式,
-
innerKeySelector Func<TInner,TKey> 用于從第二個序列的每個元素提取聯接鍵的函式,
-
resultSelector Func<TOuter,TInner,TResult> 用于從兩個匹配元素創建結果元素的函式,
-
comparerIEqualityComparer
用于對鍵進行哈希處理和比較的 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#基礎系列補全篇講解
*/
GroupJoin 關聯兩個資料源,并分組
基于鍵值等同性將兩個序列的元素進行關聯,并對結果進行分組,以上是官方介紹,我在開發程序中并沒有使用過這個方法,不過這個方法完全可以認為是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
要聯接的第一個序列, -
inner IEnumerable
要與第一個序列聯接的序列, -
outerKeySelector Func<TOuter,TKey> 用于從第一個序列的每個元素提取聯接鍵的函式,
-
innerKeySelector Func<TInner,TKey> 用于從第二個序列的每個元素提取聯接鍵的函式,
-
resultSelector Func<TOuter,IEnumerable
,TResult> 用于從第一個序列的元素和第二個序列的匹配元素集合中創建結果元素的函式, -
comparer IEqualityComparer
用于對鍵進行哈希處理和比較的 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.uj5u.com/net/56710.html
標籤:C#
上一篇:字串轉DateTime
