C# 基礎知識系列-7 Linq詳解

前言

在上一篇中簡單介紹了Linq的入門級用法,這一篇嘗試講解一些更加深刻的使用方法,與前一篇的結構不同的地方是,這一篇我會先介紹Linq裏的支持方法,而後以實際需求爲引導,分別以方法鏈的形式和類SQL的形式寫出來。html

前置概念介紹

  1. Predicate<T> 謂詞、斷言,等價於 Func<T,bool> 即返回bool的表達式
  2. Expression<TDelegate> 表達式樹,這個類很關鍵,可是在這裏會細說,咱們會講它的一個特殊的泛型類型:Expression<Func<T,bool>> 這個在某些數據源的查詢中十分重要,它表明lambda表達式中一種特殊的表達式,即沒有大括號和return關鍵字的那種。

咱們先準備兩個類java

  1. 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; }
}
  1. 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> studentsIEnumerable<Subject> subjects。先忽略這兩個數據源的實際來源,由於在開發過程當中數據來源有不少種狀況,有數據庫查詢出來的結果、遠程接口返回的結果、文件讀取的結果等等。不過最後都會整理成IEnumerable<T>的子接口或實現類的對象。api

常見方法介紹

Where 過濾數據,查詢出符合條件的結果

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 分組,依照指定內容進行分組

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();

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,因此沒找到,:-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獲取數據源中最大的一個,不過只能是數字類型的,其餘類型由於不能直接比較大小因此能夠有替代方法,就是先排序取第一個。

如下是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<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#基礎系列補全篇講解
*/

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<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的另外一種查詢方式的內容文章。

更多內容煩請關注個人博客

file

原文出處:https://www.cnblogs.com/c7jie/p/12632800.html

相關文章
相關標籤/搜索