LINQ語法簡介 - 4

by vivid 4. 四月 2018 05:27

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N180419401
出刊日期: 2018/4/4

在這篇文章中,將延續《LINQ語法簡介 - 1》、《LINQ語法簡介 - 2》與《LINQ語法簡介 - 3》文章的情境,介紹常用的LINQ運算子(Operator),以透過更簡易的語法來查詢陣列或集合中的內容。

 

比較運算子 - SequenceEqual

「SequenceEqual」是唯一的一個比較運算子,對於基礎資料型別(Primitive Data Types)而言,「SequenceEqual」運算子比較陣列或集合中的每一個項目的個數與值是否完全相同,若完全相同則回傳「True」,否則則回傳「False」,參考以下範例程式碼:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
List<int> list2 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
var result = list1.SequenceEqual( list2 );
Console.WriteLine( $"Result : {result}" );

這個範例程式的執行結果參考如下:

Result : True

只要其中有一個項目不同,比較就不相等,參考以下範例程式碼,集合中第一、二項目的值不同:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
List<int> list2 = new List<int>( ) { 2 , 1 , 3 , 4 , 5 };
var result = list1.SequenceEqual( list2 );
Console.WriteLine( $"Result : {result}" );

這個範例程式的執行結果參考如下:

Result : False

同樣地,集合中項目個數不同,比較就不相等,參考以下範例程式碼:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
List<int> list2 = new List<int>( ) { 1 , 2 , 3 , 4 };
var result = list1.SequenceEqual( list2 );
Console.WriteLine( $"Result : {result}" );

這個範例程式的執行結果參考如下:

Result : False

提示:C# LINQ查詢運算式目前不支援 「SequenceEqual」語法。

 

複雜型別比較

對於複雜型別來說,「SequenceEqual」運算子檢查兩個物件的參考來判斷兩個序列是否相等。例如以下範例程式碼,「customers1」、「customers2」兩個集合中存放相同的「Customer」物件:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
  class Customer {
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public string ContactName { get; set; }
    public string City { get; set; }
    public int PostalCode { get; set; }
    public string Country { get; set; }
  }
  class Program {
    static void Main( string [] args ) {

      var customer = new Customer( ) { CustomerID = 1 , CustomerName = "Mary " , ContactName = "Maria Anders" , City = "Berlin" , PostalCode = 12209 , Country = "Germany" };

      List<Customer> customers1 = new List<Customer> { customer };
      List<Customer> customers2 = new List<Customer> { customer };

      var result = customers1.SequenceEqual( customers2 );
      Console.WriteLine( $"Result : {result}" );
    }
  }
}

這個範例程式的執行結果參考如下:

Result : True

若程式改寫如下,兩集合中各新增一個屬性值完全相同的「Customer」物件:

List<Customer> customers1 = new List<Customer> { new Customer( ) { CustomerID = 1 , CustomerName = "Mary " , ContactName = "Maria Anders" , City = "Berlin" , PostalCode = 12209 , Country = "Germany" }};
List<Customer> customers2 = new List<Customer> { new Customer( ) { CustomerID = 1 , CustomerName = "Mary " , ContactName = "Maria Anders" , City = "Berlin" , PostalCode = 12209 , Country = "Germany" }};
var result = customers1.SequenceEqual( customers2 );
Console.WriteLine( $"Result : {result}" );

 

因為比較的是物件的參考,所以這個範例的執行結果會回傳「false」:

Result : False

我們可以自訂一個類別,實作「IEqualityComparer<T>」介面來自訂比較的規則,參考以下範例程式碼,「MyComparer」類別實作「IEqualityComparer< Customer >」介面,並改寫「Equals」方法,在兩個序列中當「Customer」物件之「CustomerID」與「CustomerName」屬性值相同時,將兩序列視為相等。接著在叫用「SequenceEqual」方法時,傳入「MyComparer」實體:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
  class MyComparer : IEqualityComparer<Customer> {
    public bool Equals( Customer x , Customer y ) {
      if ( x.CustomerID == y.CustomerID && x.CustomerName == y.CustomerName ) {
        return true;
      } else {
        return false;
      }
    }
    public int GetHashCode( Customer obj ) => obj.GetHashCode( );
  }
  class Customer {
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public string ContactName { get; set; }
    public string City { get; set; }
    public int PostalCode { get; set; }
    public string Country { get; set; }
  }
  class Program {
    static void Main( string [] args ) {
      List<Customer> customers1 = new List<Customer> { new Customer( ) { CustomerID = 1 , CustomerName = "Mary " , ContactName = "Maria Anders" , City = "Berlin" , PostalCode = 12209 , Country = "Germany" } };
      List<Customer> customers2 = new List<Customer> { new Customer( ) { CustomerID = 1 , CustomerName = "Mary " , ContactName = "Maria Anders" , City = "Berlin" , PostalCode = 12209 , Country = "Germany" } };
      var result = customers1.SequenceEqual( customers2 , new MyComparer( ) );
      Console.WriteLine( $"Result : {result}" );
    }
  }
}

 

這個範例程式的執行結果參考如下:

Result : True

 

Concat運算子

「Concat」運算子可以將兩個序列合併在一起,並回傳一個新的序列。參考以下範例程式碼:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 };
List<int> list2 = new List<int>( ) { 4 , 5 , 6 };
var result = list1.Concat( list2 );

foreach ( var item in result ) {
  Console.WriteLine( item );
}


這個範例程式的執行結果參考如下:

1
2
3
4
5
6

提示:C# LINQ查詢運算式目前不支援 「Concat」語法。

 

Zip運算子

「Zip」運算子類似「Concat」運算子可以將兩個序列合併在一起,「Zip」運算子利用一個Func委派進行一些計算,並回傳一個新的序列。參考以下範例程式碼,將兩個集合的項目,按順序相加:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 };
List<int> list2 = new List<int>( ) { 4 , 5 , 6 };
var result = list1.Zip( list2 , ( i , j ) => i + j );

foreach ( var item in result ) {
  Console.WriteLine( item );
}

 

這個範例程式的執行結果參考如下:

5
7
9

提示:C# LINQ查詢運算式目前不支援 「Zip」語法。

 

Generation運算子 - DefaultIfEmpty

「DefaultIfEmpty」會回傳一個新的集合,若原始集合或陣列不是空集合(空陣列),則「DefaultIfEmpty」就會將原集合或陣列中的資料,複製到新集合或陣列新陣列中;若原始原始集合或陣列沒有任何項目,則新集合會包含一個項目,項目的預設值則根據原始集合或陣列的型別決定。

參考以下範例程式碼,若原始集合不是空集合,則「DefaultIfEmpty」就會將原集合中的資料,複製到新集合中:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 };
var result = list1.DefaultIfEmpty( );
Console.WriteLine( $"Count : {result.Count( )}" ); //3
foreach ( var item in list1 ) {
   Console.WriteLine(item);
}

 

這個範例程式的執行結果參考如下:

Count : 3
1
2
3

若原始集合的型別為「string」型別,則「DefaultIfEmpty」在空集合的情況下,回傳的新集合第一個項目的預設值為「null」,參考以下範例程式碼:

List<string> list1 = new List<string>( );
var result = list1.DefaultIfEmpty( );
Console.WriteLine( $"Count : {result.Count( )}" ); //1
Console.WriteLine( result.First( ) == null ); // True

這個範例程式的執行結果參考如下:

Count : 1
True

若原始集合的型別為「int」型別,則「DefaultIfEmpty」在空集合的情況下,回傳的新集合第一個項目的預設值為「0」:

List<int> list1 = new List<int>( );
var result = list1.DefaultIfEmpty( );
Console.WriteLine( $"Count : {result.Count( )}" ); //1
Console.WriteLine( result.First( ) ); // 0

「DefaultIfEmpty」還可以指定要回傳的預設值,參考以下範例程式碼,當原始集合的型別為「int」型別,則「DefaultIfEmpty」在空集合的情況下,設定回傳的新集合第一個項目的預設值為「100」:

List<int> list1 = new List<int>( );
var result = list1.DefaultIfEmpty( 100 );
Console.WriteLine( $"Count : {result.Count( )}" ); //1
Console.WriteLine( result.First( ) ); // 100

這個範例程式的執行結果參考如下:

Count : 1
100

提示:C# LINQ查詢運算式目前不支援 「DefaultIfEmpty」語法。

 

Generation運算子 - Empty

「Empty」運算子用來回傳一個空的陣列或集合,參考以下範例程式碼,回傳一個空白的字串陣列:

var list = Enumerable.Empty<string>( );
Console.WriteLine( $"Count : {list.Count( )}" ); // Count : 0
Console.WriteLine( $"Type : {list.GetType()}" ); // Type : System.String[]
提示:C# LINQ查詢運算式目前不支援 「Empty」語法。

 

Generation運算子 - Range

「Range」方法會產生指定個數的項目,放到一個集合中回傳,參考以下範例程式碼,叫用「Range」方法產生一個集合,存放1到10數值的項目。:

var list = Enumerable.Range( 1 , 10 );
foreach ( var item in list ) {
  Console.WriteLine(item);
}

「Range」方法的第一個參數用來指定初始值,第二個參數是要產生的項目個數。這個範例程式的執行結果參考如下:

clip_image002

圖 1:Range。

Generation運算子 - Repeat

「Repeat」方法用來產生重複的項目,參考以下範例程式碼,重複產生數值「1」五次:

var list = Enumerable.Repeat( 1 , 5 );
foreach ( var item in list ) {
  Console.WriteLine(item);
}

這個範例程式的執行結果參考如下:

clip_image004

圖 2:Repeat。

Set 運算子 - Distinct

「Distinct」用來找出陣列或集合中唯一值,參考以下範例程式碼:

int [] list = new int [] { 10 , 1 , 33 , 10 , 1 , 5 };
foreach ( var item in list.Distinct() ) {
  Console.WriteLine(item);
}

這個範例程式的執行結果參考如下:

clip_image006

圖 3:Distinct。

針對複雜型別,預設「Distinct」無法比對唯一性,參考以下範例程式碼,集合中包含多個屬性值完全相同的物件:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
  class Customer {
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public string ContactName { get; set; }
    public string City { get; set; }
    public int PostalCode { get; set; }
    public string Country { get; set; }
  }
  class Program {
    static void Main( string [] args ) {

      List<Customer> customers = new List<Customer> {
        new Customer(){ CustomerID = 1, CustomerName ="Mary " , ContactName = "Maria Anders" , City = "Berlin", PostalCode = 12209 , Country = "Germany" },
        new Customer(){ CustomerID = 1, CustomerName ="Mary " , ContactName = "Maria Anders" , City = "Berlin", PostalCode = 12209 , Country = "Germany" },
        new Customer(){ CustomerID = 2, CustomerName ="Ann " , ContactName = "Ana Trujillo" , City = "México ", PostalCode = 05021 , Country = "Mexico" },
        new Customer(){ CustomerID = 2, CustomerName ="Ann " , ContactName = "Ana Trujillo" , City = "México ", PostalCode = 05021 , Country = "Mexico" },
        new Customer(){ CustomerID = 3, CustomerName ="Lili" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "UK" }
      };

      foreach ( var item in customers.Distinct( ) ) {
        Console.WriteLine( $" {item.CustomerID} - {item.CustomerName} - {item.ContactName}" );
      }

    }
  }
}

 
這個範例程式的執行結果參考如下,集合中的「Customer」都會被視為唯一的物件:

clip_image008

圖 4:Distinct。

我們需要實作「IEqualityComparer<T>」介面來比較複雜型別,參考以下範例程式碼,在叫用「Distinct」方法時,傳入「MyComparer」物件:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
  class MyComparer : IEqualityComparer<Customer> {
    public bool Equals( Customer x , Customer y ) {
      if ( x.CustomerID == y.CustomerID && x.CustomerName == y.CustomerName ) {
        return true;
      } else {
        return false;
      }
    }
    public int GetHashCode( Customer obj ) => obj.CustomerID.GetHashCode( );
  }
  class Customer {
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public string ContactName { get; set; }
    public string City { get; set; }
    public int PostalCode { get; set; }
    public string Country { get; set; }
  }
  class Program {
    static void Main( string [] args ) {

      List<Customer> customers = new List<Customer> {
        new Customer(){ CustomerID = 1, CustomerName ="Mary" , ContactName = "Maria Anders" , City = "Berlin", PostalCode = 12209 , Country = "Germany" },
        new Customer(){ CustomerID = 1, CustomerName ="Mary" , ContactName = "Maria Anders" , City = "Berlin", PostalCode = 12209 , Country = "Germany" },
        new Customer(){ CustomerID = 2, CustomerName ="Ann" , ContactName = "Ana Trujillo" , City = "México ", PostalCode = 05021 , Country = "Mexico" },
        new Customer(){ CustomerID = 2, CustomerName ="Ann" , ContactName = "Ana Trujillo" , City = "México ", PostalCode = 05021 , Country = "Mexico" },
        new Customer(){ CustomerID = 3, CustomerName ="Lili" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "UK" }
      };

      foreach ( var item in customers.Distinct( new MyComparer() ) ) {
        Console.WriteLine( $" {item.CustomerID} - {item.CustomerName} - {item.ContactName}" );
      }

    }
  }
}

 

這個範例程式的執行結果參考如下:

clip_image010

圖 5:Distinct。

Set 運算子 - Except

「Except」有減去的效果,從一個集合中,移除和另一個集合重複的內容,參考以下範例程式碼,兩個集合中都出現「4」、「5」兩個項目:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
List<int> list2 = new List<int>( ) { 4 , 5 , 6 , 7 , 8 };
var result = list1.Except( list2 );
foreach ( var item in result ) {
  Console.WriteLine( item );
}

這個範例程式的執行結果參考如下,新集合中不包含「4」、「5」兩個項目:

clip_image012

圖 6:Except。

Set 運算子 - Intersect

「Intersect」有取交集的效果,找出兩個集合中重複的內容,參考以下範例程式碼:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
List<int> list2 = new List<int>( ) { 4 , 5 , 6 , 7 , 8 };
var result = list1.Intersect( list2 );
foreach ( var item in result ) {
  Console.WriteLine( item );
}

這個範例程式的執行結果參考如下:

clip_image014

圖 7:Intersect。

Set 運算子 - Union

「Union」有取聯集的效果,找出兩個集合中不重複的內容,參考以下範例程式碼:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
List<int> list2 = new List<int>( ) { 4 , 5 , 6 , 7 , 8 };
var result = list1.Union( list2 );
foreach ( var item in result ) {
  Console.WriteLine( item );
}

這個範例程式的執行結果參考如下:

clip_image016

圖 8:Union。

Partitioning 運算子 - Skip

「Skip」會跳過指定個數的項目,將剩餘的項目放在集合中回傳,參考以下範例程式碼,跳過前兩個項目:

List<int> list1 = new List<int>( ) { 1 , 2 , 3 , 4 , 5 };
var result = list1.Skip( 2 );
foreach ( var item in result ) {
  Console.WriteLine( item );
}

這個範例程式的執行結果參考如下:

clip_image018

圖 9:Skip。

Partitioning 運算子 - SkipWhile

「SkipWhile」可以根據條件來跳過項目,將剩餘的項目放在集合中回傳,參考以下範例程式碼,跳過集合中項目值小於「4」的項目:

List<int> list1 = new List<int>( ) { 1 , 2 , 5 , 4 , 3 };
var result = list1.SkipWhile( i => i < 4 );
foreach ( var item in result ) {
  Console.WriteLine( item );
}

這個範例程式的執行結果參考如下:

clip_image020

圖 10:SkipWhile。

範例中集合的第一、二個項目丟到篩選條件式比對都會得到「true」,因此跳過這兩個項目。「SkipWhile」只要找到一個項目符合條件就會停止跳過的動作,因此這個範例中集合中第三個項目「5」丟到篩選條件式比對 「5 < 4」為「false」,接著就停止比對的動作。

 

Partitioning 運算子 - Take

「Take」從陣列或集合中,從頭開始回傳指定個數的項目,參考以下範例程式碼,取得前三個項目:

List<int> list1 = new List<int>( ) { 1 , 2 , 5 , 4 , 3 };
var result = list1.Take( 3 );
foreach ( var item in result ) {
  Console.WriteLine( item );
}

這個範例程式的執行結果參考如下:

clip_image022

圖 11:Take。

Partitioning 運算子 - TakeWhile

「TakeWhile」從陣列或集合中,將滿足條件的項目回傳,只要找到其中一個項目滿足條件,就停止。參考以下範例程式碼,取得前2個項目:

List<int> list1 = new List<int>( ) { 1 , 2 , 5 , 4 , 3 };
var result = list1.TakeWhile( i => i < 4 );
foreach ( var item in result ) {
  Console.WriteLine( item );
}

這個範例程式的執行結果參考如下:

clip_image024

圖 12:TakeWhile。

Tags:

.NET Magazine國際中文電子雜誌 | C# | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List