天天看點

[C#.NET 拾遺補漏]08:強大的LINQ一些基礎First、Last 和 Single 等Except 取差集SelectMany 集合降維SelectMany 迪卡爾積運算Aggregate 聚合Join 關聯查詢Skip & Take 分頁Zip 拉鍊OfType 和 Cast 類型過濾與轉換ToLookup 索引式查找Distinct 去重ToDictionary 字典轉換其它常見擴充方法最後

目錄

一些基礎

First、Last 和 Single 等

Except 取差集

SelectMany 集合降維

SelectMany 迪卡爾積運算

Aggregate 聚合

Join 關聯查詢

Inner Join

Left Join

Right Join

Cross Join

Full Outer Join

根據多個鍵關聯

Skip & Take 分頁

Zip 拉鍊

OfType 和 Cast 類型過濾與轉換

ToLookup 索引式查找

Distinct 去重

ToDictionary 字典轉換

其它常見擴充方法

Range 和 Repeat

Any 和 All

Concat 和 Union

GroupBy 分組

DefaultIfEmpty 空替換

SequenceEqual 集合相等

最後

閱讀本文大概需要 13 分鐘。

大家好,這是 [C#.NET 拾遺補漏] 系列的第 08 篇文章,今天講 C# 強大的 LINQ 查詢。LINQ 是我最喜歡的 C# 語言特性之一。

LINQ 是 Language INtegrated Query 單詞的首字母縮寫,翻譯過來是語言內建查詢。它為查詢跨各種資料源和格式的資料提供了一緻的模型,是以叫內建查詢。由于這種查詢并沒有制造新的語言而隻是在現有的語言基礎上來實作,是以叫語言內建查詢。

一些基礎

在 C# 中,從功能上 LINQ 可分為兩類:LINQ to Object 和 LINQ to Provider(如:XML);從文法上 LINQ 可以分為 LINQ to Object 和 LINQ 擴充方法。大多數 LINQ to Object 都可以用 LINQ 擴充方法實作等同的效果,而且平時開發中用的最多的是 LINQ 擴充方法。

LINQ to Object 多用于映射資料庫的查詢,LINQ to XML 用于查詢 XML 元素資料。使用 LINQ 查詢的前提是對象必須是一個 IEnumerable 集合(注意,為了描述友善,本文說的集合都是指 IEnumerable 對象,包含字面上的 ICollection 對象)。另外,LINQ 查詢大多是都是鍊式查詢,即操作的資料源是 

IEnumerable<T1>

 類型,傳回的是 

IEnumerable<T2>

 類型。

形如下面這樣的查詢就是 LINQ to Object:

var list = from user in users
  where user.Name.Contains("Wang")
  select user.Id;
           

等同于使用下面的 LINQ 擴充方法:

var list = users
  .Where(u => user.Name.Contains("Wang"))
  .Select(u => u.id);
           

LINQ 查詢支援在語句中間根據需要定義變量,比如取出數組中平方值大于平均值的數字:

int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result = from number in numbers
            let average = numbers.Average()
            let squared = Math.Pow(number, 2)
            where squared > average
            select number;
// 平均值為 4.5, result 為 { 3, 4, 5, 6, 7, 8, 9 }
           

其中的 Select 方法接收的參數用的最多的是 

Func<TSource, TResult>

,它還可以接收 

Func<TSource, int, TResult>

 參數,示例:

var collectionWithRowNumber = collection.
    .Select((item, index) => new { Item = item, RowNumber =index })
    .ToList();
           

再來看一下 LINQ to XML 的示例。假如我們有如下 XML 檔案:

<?xml version="1.0" encoding="utf-8" ?>
<Employees>
  <Employee>
    <EmpId>1</EmpId>
    <Name>Liam</Name>
    <Sex>男</Sex>
  </Employee>
  <Employee>
    <EmpId>2</EmpId>
    ...
  </Employee>
</Employees>
           

使用 LINQ to XML 查詢所有含有指定節點值的元素:

XElement xelement = XElement.Load("Employees.xml");
var els = from el in xelement.Elements("Employee")
          where (string)el.Element("Sex") == "Male"
          select el;
           

等同于使用 LINQ 擴充方法:

var els = xelement.Elements("Employee")
    .Where(el => (string)el.Element("Sex") == "Male");
           

LINQ to XML 操作 XML 非常友善和靈活,大家可以在具體使用的時候去探索,這裡就不展開講了。

LINQ 查詢有很多方法,由于篇幅原因,就不一一列舉示範了,這裡隻選取一些強大的查詢方法,這些方法若使用非 LINQ 來實作可能會比較麻煩。

LINQ 之是以強大,是因為它可以輕松實作複雜的查詢,下面我們來總結一下 C# LINQ 的強大之處。

First、Last 和 Single 等

First、FirstOrDefault、Last、LastOrDefault、Single 和 SingleOrDefault 是快速查詢集合中的第一個或最後一個元素的方法。如果集合是空的,Fist、Last 和 Single 都會報錯,如果使其不報錯而在空集合時使用預設值可以使用 FirstOrDefault、LastOrDefault 和 SingleOrDefault。Single/SingleOrDefault 和其它方法的差別是,它限定查詢結果隻有一個元素,如果查詢結果集合中包含多個元素時會報錯。具體看下面幾個示例:

new[] { "a", "b" }.First(x => x.Equals("b")); // 傳回 ”b“
new[] { "a", "b" }.First(x => x.Equals("c")); // 抛出 InvalidOperationException 異常
new[] { "a", "b" }.FirstOrDefault(x => x.Equals("c")); // 傳回 null

new[] { "a", "b" }.Single(x => x.Equals("b")); // 傳回 ”b“
new[] { "a", "b" }.Single(x => x.Equals("c")); // 抛出 InvalidOperationException 異常
new[] { "a", "b" }.SingleOrDefault(x => x.Equals("c")); // 傳回 null
new[] { "a", "a" }.Single(); // 抛出 InvalidOperationException 異常
           

在實際應用中,如果要確定查詢結果的唯一性(比如通過手機号查詢使用者),使用 Single/SingleOrDefaut,其它情況應盡量使用 First/FirstOrDefault。雖然 FirstOrDefault 也可以根據條件判斷元素是否存在,但使用 Any 更高效。

Except 取差集

LINQ 的 Except 方法用來取差集,即取出集合中與另一個集合所有元素不同的元素。

示例:

int[] first = { 1, 2, 3, 4 };
int[] second = { 0, 2, 3, 5 };
IEnumerable<int> result = first.Except(second);
// result = { 1, 4 }
           

注意 Except 方法會去除重複元素:

int[] second = { 0, 2, 3, 5 };
int[] third = { 1, 1, 1, 2, 3, 4 };
IEnumerable<int> result = third.Except(second);
// result = { 1, 4 }
           

對于簡單類型(int、float、string 等)使用 Except 很簡單,但對于自定義類型(或者叫複合類型,下同)的 Object 如何使用 Except 呢?此時需要将自定義類型實作

IEquatable<T>

接口,示例:

class User : IEquatable<User>
{
    public string Name { get; set; }

    public bool Equals(User other)
    {
        return Name == other.Name;
    }

    public override int GetHashCode()
    {
        return Name?.GetHashCode() ?? 0;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var list1 = new List<User>
        {
            new User{ Name = "User1"},
            new User{ Name = "User2"},
        };

        var list2 = new List<User>
        {
            new User{ Name = "User2"},
            new User{ Name = "User3"},
        };

        var result = list1.Except(list2);
        result.ForEach(u => Console.WriteLine(u.Name));
        // 輸出:User1
    }
}
           

SelectMany 集合降維

SelectMany 可以把多元集合降維,比如把二維的集合平鋪成一個一維的集合。舉例:

var collection = new int[][]
{
    new int[] {1, 2, 3},
    new int[] {4, 5, 6},
};
var result = collection.SelectMany(x => x);
// result = [1, 2, 3, 4, 5, 6]
           

再來舉個更貼合實際應用的例子。例如有如下實體類(一個部門有多個員工):

class Department
{
    public Employee[] Employees { get; set; }
}

class Employee
{
    public string Name { get; set; }
}
           

此時,我們擁有一個這樣的資料集合:

var departments = new[]
{
    new Department()
    {
        Employees = new []
        {
            new Employee { Name = "Bob" },
            new Employee { Name = "Jack" }
        }
    },
    new Department()
    {
        Employees = new []
        {
            new Employee { Name = "Jim" },
            new Employee { Name = "John" }
        }
    }
};
           

現在我們可以使用 SelectMany 把各部門的員工查詢到一個結果集中:

var allEmployees = departments.SelectMany(x => x.Employees);
foreach(var emp in allEmployees)
{
    Console.WriteLine(emp.Name);
}
// 依次輸出:Bob Jack Jim John
           

SelectMany 迪卡爾積運算

SelectMany 不光适用于單個包含多元集合對象的降維,也适用于多個集合之前的兩兩互相操作,比如進行迪卡爾積運算。比如我們有這樣兩個集合:

var list1 = new List<string> { "a1", "a2" };
var list2 = new List<string> { "b1", "b2", "b3" };
           

現在我們需要把它進行兩兩組合,使用普通的方法,我們需要用嵌套循環語句來實作:

var result = newList<string>();
foreach (var s1 in list1)
    foreach (var s2 in list2)
        result.Add($"{s1}{s2}");
// result = ["a1b1", "a1b2", "a1b3", "a2b1", "a2b2", "a2b3"]
           

改用 SelectMany 實作:

var result = list1.SelectMany(x => list2.Select(y => $"{x}{y}"));
// result = ["a1b1", "a1b2", "a1b3", "a2b1", "a2b2", "a2b3"]
           

具有挑戰性的問題來了,如何對 N 個集合進行迪卡爾積運算呢,比如有這樣的集合資料:

var arrList = new List<string[]>
{
    new string[] { "a1", "a2" },
    new string[] { "b1", "b2", "b3" },
    new string[] { "c1" },
    // ...
};
           

如何對上面的 arrList 中的各個集合進行兩兩組合呢?在電商業務尤其是零售業務中的産品組合促銷中這種需求很常見。

下面是一個使用 SelectMany 的實作,需要用到遞歸:

class Program
{
    static void Main(string[] args)
    {
        var arrList = new List<string[]>
        {
            new string[] { "a1", "a2" },
            new string[] { "b1", "b2", "b3" },
            new string[] { "c1" },
            // ...
        };

        var result = Recursion(arrList, 0, new List<string>());
        result.ForEach(x => Console.WriteLine(x));
    }

    static List<string> Recursion(List<string[]> list, int start, List<string> result)
    {
        if (start >= list.Count)
            return result;

        if (result.Count == 0)
            result = list[start].ToList();
        else
            result = result.SelectMany(x => list[start].Select(y => x + y)).ToList();

        result = Recursion(list, start + 1, result);

        return result;
    }
}
           

輸出:

a1b1c1
a1b2c1
a1b3c1
a2b1c1
a2b2c1
a2b3c1
           

類似這種集合的迪卡爾積運算操作,也可以用 LINQ to Object 來代替 SelectMany 實作:

result = result.SelectMany(x => list[start].Select(y => x + y)).ToList();
// 等同使用擴充方法:
result = (from a in result from b in list[start] select a + b).ToList();
           

LINQ to Object 比擴充方法看上去易讀性更好,但寫起來擴充方法更友善。

Aggregate 聚合

Aggregate 擴充方法可以對一個集合依次執行類似累加器的操作,就像滾雪球一樣把資料逐漸聚集在一起。比如實作從 1 加到 10,用 Aggregate 擴充方法就很友善:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum = numbers.Aggregate((prevSum, current) => prevSum + current);
// sum = 55
           

我們來解析一下它的執行步驟

  • 第一步,prevSum 取第一個元素的值,即 prevSum = 1
  • 第二步,把第一步得到的 prevSum 的值加上第二個元素,即 prevSum = prevSum + 2
  • 依此類推,第 i 步把第 i-1 得到的 prevSum 加上第 i 個元素

再來看一個字元串的例子加深了解:

string[] stringList = { "Hello", "World", "!" };
string joinedString = stringList.Aggregate((prev, current) => prev + " " + current);
// joinedString = "Hello World !"
           

Aggregate 還有一個重載方法,可以指定累加器的初始值。我們來看一個比較綜合的複雜例子。假如我們有如下 1-12 的一個數字集合:

var items = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
           

現在我們想做如下計算:

  • 計算集合元素的總數個數
  • 計算值為偶數的元素個數
  • 收集每第 4 個元素

當然通過普通的循環周遊也可以實作這三個計算,但使用 Aggregate 會更簡潔,下面是 Aggregate 的實作:

var result = items.Aggregate(new { Total = 0, Even = 0, FourthItems = new List<int>() },
    (accum, item) =>
    new
    {
        Total = accum.Total + 1,
        Even = accum.Even + (item % 2 == 0 ? 1 : 0),
        FourthItems = (accum.Total + 1) % 4 == 0 ? new List<int>(accum.FourthItems) { item } : accum.FourthItems
    }
);

// result:
// Total = 12
// Even = 6
// FourthItems = [4, 8, 12]
           

這裡為了簡單起見使用匿名類型作為累加器的初始值,由于匿名類型的屬性是隻讀的,是以在累加的過程都 new 了一個新對象。如果初始值使用的是自定義類型,那累加時就不需 new 新對象了。

Join 關聯查詢

和 SQL 查詢一樣,LINQ 同樣支援 Inner Join、Left Join、Right Join、Cross Join 和 Full Outer Join,有時候你可能看到不同的寫法,其實是同一個意思,比如 Left Outer Join 就是 Left Join,Join 是 Inner Join 省略了 Inner 等。

假設我們有下面兩個集合,分别表示左邊的資料和右邊的資料。

var first = new List<string>() { "a","b","c" }; // 左邊
var second = new List<string>() { "a", "c", "d" }; // 右邊
           

下面以此資料為例來示範各種關聯查詢。

Inner Join

var result = from f in first
              join s in second on f equals s
              select new { f, s };
// 等同使用擴充方法:
var result = first.Join(second,
    f => f,
    s => s,
    (f, s) => new { f, s });

// result: {"a","a"}
//         {"c","c"}
           

Left Join

var result = from f in first
            join s in second on f equals s into temp
            from t in temp.DefaultIfEmpty()
            select new { First = f, Second = t };
// 或者:
var result = from f in first
            from s in second.Where(x => x == f).DefaultIfEmpty()
            select new { First = f, Second = s };

// 等同使用擴充方法:
var result = first.GroupJoin(second,
        f => f,
        s => s,
        (f, s) => new { First = f, Second = s })
    .SelectMany(temp => temp.Second.DefaultIfEmpty(),
        (f, s) => new { First = f.First, Second = s });

// result: {"a","a"}
//         {"b", null}
//         {"c","c"}
           

Right Join

var result = from s in second
            join f in first on s equals f into temp
            from t in temp.DefaultIfEmpty()
            select new { First = t, Second = s };
// 其它和 Left Join 類似

// result: {"a","a"}
//         {"c","c"}
//         {null,"d"}
           

Cross Join

var result = from f in first
            from s in second
            select new { f, s };

// result: {"a","a"}
//         {"a","c"}
//         {"a","d"}
//         {"b","a"}
//         {"b","c"}
//         {"b","d"}
//         {"c","a"}
//         {"c","c"}
//         {"c","d"}
           

Full Outer Join

var leftJoin = from f in first
            join s in second on f equals s into temp
            from t in temp.DefaultIfEmpty()
            select new { First = f, Second = t };
var rightJoin = from s in second
            join f in first on s equals f into temp
            from t in temp.DefaultIfEmpty()
            select new { First = t, Second = s };
var fullOuterJoin = leftJoin.Union(rightJoin);
           

根據多個鍵關聯

在 SQL 中,表與表進行關聯查詢時 on 條件可以指定多個鍵的邏輯判斷,用 and 或 or 連接配接。但 C# 的 LINQ 不支援 and 關鍵字,若要根據多鍵關聯,需要把要關聯的鍵值分别以相同的屬性名放到匿名對象中,然後使用 equals 比較兩個匿名對象是否相等。示例:

var stringProps = typeof(string).GetProperties();
var builderProps = typeof(StringBuilder).GetProperties();
var query =
    from s in stringProps
    join b in builderProps
    on new { s.Name, s.PropertyType } equals new { b.Name, b.PropertyType }
    select new
    {
        s.Name,
        s.PropertyType
    };
           

以上均使用兩個集合做為示例,LINQ 關聯查詢也支援多個集合關聯,就像 SQL 的多表關聯,隻需往後繼續追加 join 操作即可,不再累述。

LINQ 關聯查與 SQL 相似,但使用上有很大差別。LINQ 關聯查詢的用法有很多,也很靈活,不用刻意去記住它們,隻要熟悉簡單常用的,其它的在實際用到的時候再查詢相關文檔。

Skip & Take 分頁

Skip 擴充方法用來跳過從起始位置開始的指定數量的元素讀取集合;Take 擴充方法用來從集合中隻讀取指定數量的元素。

var values = new[] { 5, 4, 3, 2, 1 };
var skipTwo = values.Skip(2);  // { 3, 2, 1 }
var takeThree = values.Take(3);  // { 5, 4, 3 }
var skipOneTakeTwo = values.Skip(1).Take(2); // { 4, 3 }
           

Skip 與 Take 兩個方法結合即可實作我們常見的分頁查詢:

public IEnumerable<T> GetPage<T>(this IEnumerable<T> collection, int pageNumber, int pageSize)
{
    int startIndex = (pageNumber - 1) * pageSize;
    return collection.Skip(startIndex).Take(pageSize);
}
           

使用過 EF (Core) 的同學一定很熟悉。

另外,還有 SkipWhile 和 TakeWhile 擴充方法,它與 Skip 和 Take 不同的是,它們的參數是具體的條件。SkipWhile 從起始位置開始忽略元素,直到比對到符合條件的元素停止忽略,往後就是要查詢的結果;TakeWhile 從起始位置開始讀取符合條件的元素,一旦遇到不符合條件的就停止讀取,即使後面還有符合條件的也不再讀取。示例:

SkipWhile:

int[] list = { 42, 42, 6, 6, 6, 42 };
var result = list.SkipWhile(i => i == 42);
// result: 6, 6, 6, 42
           

TakeWhile:

int[] list = { 1, 10, 40, 50, 44, 70, 4 };
var result = list.TakeWhile(item => item < 50).ToList();
// result = { 1, 10, 40 }
           

Zip 拉鍊

Zip 擴充方法操作的對象是兩個集合,它就像拉鍊一樣,根據位置将兩個系列中的每個元素依次配對在一起。其接收的參數是一個 Func 執行個體,該 Func 執行個體允許我們成對在處理兩個集合中的元素。如果兩個集合中的元素個數不相等,那麼多出來的将會被忽略。

示例:

int[] numbers = { 3, 5, 7 };
string[] words = { "three", "five", "seven", "ignored" };
IEnumerable<string> zip = numbers.Zip(words, (n, w) => n + "=" + w);

foreach (string s in zip)
{
    Console.WriteLine(s);
}
           

輸出:

3=three
5=five
7=seven
           

OfType 和 Cast 類型過濾與轉換

OfType 用于篩選集合中指定類型的元素,Cast 可以把集合轉換為指定類型,但要求源類型必須可以隐式轉換為目标類型。假如有如下資料:

interface IFoo { }
class Foo : IFoo { }
class Bar : IFoo { }

var item0 = new Foo();
var item1 = new Foo();
var item2 = new Bar();
var item3 = new Bar();
var collection = new IFoo[] { item0, item1, item2, item3 };
           

OfType 示例:

var foos = collection.OfType<Foo>(); // result: item0, item1
var bars = collection.OfType<Bar>(); // result: item2, item3
var foosAndBars = collection.OfType<IFoo>(); // result: item0, item1, item2, item3

// 等同于使用 Where
var foos = collection.Where(item => item is Foo); // result: item0, item1
var bars = collection.Where(item => item is Bar); // result: item2, item3
           

Cast 示例:

var bars = collection.Cast<Bar>();  // InvalidCastException 異常
var foos = collection.Cast<Foo>();  // InvalidCastException 異常
var foosAndBars = collection.Cast<IFoo>();  // OK
           

ToLookup 索引式查找

ToLookup 擴充方法傳回的是可索引查找的資料結構,它是一個 ILookup 執行個體,所有元素根據指定的鍵進行分組并可以按鍵進行索引。這樣說有點抽象,來看具體示例:

string[] array = { "one", "two", "three" };
// 根據元素字元串長度建立一個查找對象
var lookup = array.ToLookup(item => item.Length);

// 查找字元串長度為 3 的元素
var result = lookup[3];
// result: one,two
           

再來一個示例:

int[] array = { 1,2,3,4,5,6,7,8 };
// 建立一個奇偶查找(鍵為 0 和 1)
var lookup = array.ToLookup(item => item % 2);

// 查找偶數
var even = lookup[0];
// even: 2,4,6,8

// 查找奇數
var odd = lookup[1];
// odd: 1,3,5,7
           

Distinct 去重

Distinct 方法用來去除重複項,這個容易了解。示例:

int[] array = { 1, 2, 3, 4, 2, 5, 3, 1, 2 };
var distinct = array.Distinct();
// distinct = { 1, 2, 3, 4, 5 }
           

簡單類型的集合調用 Distinct 方法使用的是預設的比較器,Distinct 方法用此比較器來判斷元素是否與其它元素重複,但對于自定義類型要實作去重則需要自定義比較器。示例:

public class IdEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y) => x.Id == y.Id;
    public int GetHashCode(Person p) => p.Id;
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var people = new List<Person>();
        var distinct = people.Distinct(new IdEqualityComparer());
    }
}
           

ToDictionary 字典轉換

ToDictionary 擴充方法可以把集合 

IEnumerable<TElement>

 轉換為 

Dictionary<TKey, TValue>

 結構的字典,它接收一個 

Func<TSource, TKey>

 參數用來傳回每個元素指定的鍵與值。示例:

IEnumerable<User> users = GetUsers();
Dictionary<int, User> usersById = users.ToDictionary(x => x.Id);
           

如果不用 ToDictionary,你需要這樣寫:

IEnumerable<User> users = GetUsers();
Dictionary<int, User> usersById = new Dictionary<int, User>();
foreach (User u in users)
{
    usersById.Add(u.Id, u);
}
           

上面 ToDictionary 傳回的字典資料中的值是整個元素,你也可以通過它的第二個參數來自定義字典的值。示例:

Dictionary<int, string> userNamesById = users.ToDictionary(x => x.Id, x => x.Name);
           

你也可以為轉換的字典指定其鍵是否區分大小寫,即自定義字典的 IComparer,示例:

Dictionary<string, User> usersByCaseInsenstiveName = users.ToDictionary(x =>x.Name,
    StringComparer.InvariantCultureIgnoreCase);

var user1 =usersByCaseInsenstiveName["liam"];
var user2 =usersByCaseInsenstiveName["LIAM"];
user1 == user2; // true
           

注意,字典類型要求所有鍵不能重複,是以在使用 ToDictionary 方法時要確定作為字典的鍵的元素屬性不能有重複值,否則會抛出異常。

其它常見擴充方法

LINQ 還有很多其它常見的擴充方法,大家在平時應該用的比較多,比如 Where、Any、All 等,這裡也選幾個簡單舉例介紹一下。

Range 和 Repeat

Range 和 Repeat 用于生成簡單的數字或字元串系列。示例:

// 生成 1-100 的數字,即結果為 [1, 2, ..., 99, 100]
var range = Enumerable.Range(1, 100);

// 生成三個重複的字元串“a”,即結果為 ["a", "a", "a"]
var repeatedValues = Enumerable.Repeat("a", 3);
           

Any 和 All

Any 用來判斷集合中是否存在任一一個元素符合條件,All 用來判斷集合中是否所有元素符合條件。示例:

var numbers = new int[] {1, 2, 3, 4, 5 };
bool result = numbers.Any(); // true
bool result = numbers.Any(x => x == 6); // false
bool result = numbers.All(x => x > 0); // true
bool result = numbers.All(x => x > 1); // false
           

Concat 和 Union

Concat 用來拼接兩個集合,不會去除重複元素,示例:

List<int> foo = newList<int> { 1, 2, 3 };
List<int> bar = newList<int> { 3, 4, 5 };
// 通過 Enumerable 類的靜态方法
var result = Enumerable.Concat(foo, bar).ToList(); // 1,2,3,3,4,5
// 通過擴充方法
var result = foo.Concat(bar).ToList(); // 1,2,3,3,4,5
           

Union 也是用來拼接兩個集合,與 Concat 不同的是,它會去除重複項,示例:

var result = foo.Union(bar); // 1,2,3,4,5
           

GroupBy 分組

GroupBy 擴充方法用來對集合進行分組,下面是一個根據奇偶進行分組的示例:

var list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var grouped = list.GroupBy(x => x % 2 == 0);
// grouped: [1, 3, 5, 7, 9] 和 [2, 4, 6, 8]
           

還可以根據指定屬性進行分組:

public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}

var people = new List<Person>();
var query = people
    .GroupBy(x => x.Age)
    .Select(g => { Age = g.Key, Count = g.Count() });
           

DefaultIfEmpty 空替換

在上面的關聯查詢中我們使用了 DefaultIfEmpty 擴充方法,它表示在沒有查詢到指定條件的元素時使用元素的預設值代替。其實 DefaultIfEmpty 還可以指定其它的預設值,示例:

var chars = new List<string>() { "a", "b", "c", "d" };
chars.Where(s => s.Length > 1).DefaultIfEmpty().First(); // 傳回 null
chars.DefaultIfEmpty("N/A").FirstOrDefault(); // 傳回 "a"
chars.Where(s => s.Length > 1).DefaultIfEmpty("N/A").FirstOrDefault(); // 傳回 "N/A"
           

SequenceEqual 集合相等

SequenceEqual 擴充方法用于比較集合系列各個相同位置的元素是否相等。示例:

int[] a = new int[] {1, 2, 3};
int[] b = new int[] {1, 2, 3};
int[] c = new int[] {1, 3, 2};

bool result1 = a.SequenceEqual(b); // true
bool result2 = a.SequenceEqual(c); // false
           

最後

還有一些常用和簡單的擴充方法就不舉例了,比如 OrderBy(排序)、Sum(求和)、Count(計數)、Reverse(反轉)等,同時歡迎大家補充本文遺漏的強大或好用的 LINQ 文法糖。