iT邦幫忙

DAY 26
1

分享一些學習心得系列 第 26

LINQ自學筆記-語法應用-設定方法-Range、Repeat、Empty、Distinct

本文將為大家介紹 LINQ 設定方法中,Range、Repeat、Empty、DefaultIfEmpty、Distinct 這幾個標準查詢運算子。這幾個運算子,使用都很簡單,而且在 C# 中都只能以方法架構查詢的方式呼叫。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
Range 運算子可以讓我們產生指定範圍內的「整數」序列:

public static IEnumerable<int> Range( 
    int start, 
    int count 
)

和先前介紹的 LINQ 標準查詢運算子不同,Range 是一個靜態方法,並不是擴充方法(請注意:第一個參數沒有 this 修飾子)。Range 是 Enumerable 的靜態方法,所以是用 Enumerable.Range 來呼叫,並傳入兩個 int 型別參數,第一個是整數序列的起始值,第二個參數是【數量】。注意喔,是數量,不是終止值,使用時不要搞錯了:

var squares = Enumerable.Range(2, 4).Select(i => i * i); 
foreach (var num in squares) 
{ 
    Console.WriteLine(num); 
} 
Console.WriteLine(squares.GetType());

/* 輸出:
4 
9 
16 
25
typeof (IEnumerable<Int32>)
*/

上述程式碼透過 Enumerable.Range(2, 4) 建立了一個從 2 開始,總數量為 4 的序列,也就是 2, 3, 4, 5,然後用 Select 運算子將每個項目做平方計算並指定到新的序列中,最後輸出。同時我們也驗證一下這個新序列的型別應該是 IEnumerable<int>。

Repeat 運算子可以讓我們產生一個序列,包含指定數量的元素(或稱之為項目),其方法簽名碼如下:

public static IEnumerable<TResult> Repeat<TResult>( 
    TResult element, 
    int count 
)

和 Range 運算子一樣,Repeat 其實是 Enumerable 的靜態方法,並非擴充方法。調用時,要傳入一個要放在回傳序列中的項目,以及要重覆的次數:

var echoes = Enumerable.Repeat("Hello~ ", 3); 
foreach (var s in echoes) 
{ 
    Console.WriteLine(s); 
} 
Console.WriteLine(echoes.GetType());
/* 輸出:
Hello~ 
Hello~ 
Hello~ 
typeof (IEnumerable<String>) 
*/

上述程式碼,我們透過 Enumerable.Repeat 建立一個 IEnumerable<String> 的序列,裡面包含三個 "Hello~" 字串。

Empty 運算子可以讓我們建立一個包含指定型別的空序列:

public static IEnumerable<TResult> Empty<TResult>()

Empty 運算子也是 Enumerable 的靜態方法,非擴充方法。呼叫時,不用傳入任何參數,只要告訴它要建立序列所要收容的型別就行了:

IEnumerable<Decimal> pays = Enumerable.Empty<Decimal>(); 
Console.WriteLine(pays.GetType());
// 輸出:typeof (Decimal[])

和 Empty 運算子雷同的是 DefaultIfEmpty 運算子:

public static IEnumerable<TSource> DefaultIfEmpty<TSource>(
    this IEnumerable<TSource> source
)

DefaultIfEmpty 運算子是 IEnumerable<TSource> 的擴充方法,不須傳入任何參數,在來源序列上呼叫時,若來源序列是空的,則會回傳 TSource 型別預設值:

IEnumerable<Decimal> pays = Enumerable.Empty<Decimal>();
foreach (var e in pays)
{
    Console.WriteLine(e);
}
Console.WriteLine("----------------");
Console.WriteLine(pays.DefaultIfEmpty());
/* 輸出:
----------------
0
*/

PS. 若來源序最是 null,執行時會發生 ArgumentNullException 例外。
上述程式碼利用 Empty 靜態方法產生一個 Decimal 的序列,然後列舉它,當然不會有任何輸出,最後在這個序列上呼叫 DefaultIfEmpty 運算子,會輸出 Decimal 型別預設值 0。

本文最後要談 Distinct 運算子,它會依序把來源序列中的每一個項目,拿來和輸出序列中的項目做比較,若已存在於來源序列中,則排除,反之,則加入準備回傳的輸出序列裡。請注意,在 C# 中,Distinct 運算子只能應用在方法架構查詢,但是 VB 可以寫在查詢表達式裡。以下為 Distinct 運算子的兩個多載方法:

public static IEnumerable<TSource> Distinct<TSource>( 
    this IEnumerable<TSource> source 
)
public static IEnumerable<TSource> Distinct<TSource>( 
    this IEnumerable<TSource> source, 
    IEqualityComparer<TSource> comparer 
) 

第一個多載方法不用傳入任何參數,系統會使用 TSource 型別預設的相等比較子,來判斷項目是否相等;第二個多載方法則是傳入自訂的相等比較子(IEqualityComparer<TSource> comparer),來比較項目是否相等:

void Main() 
{ 
    var list = new List<string>() {"ASUS","Acer","BenQ", "asus", "ACER", "HP"}; 
    var comparer = new IgnoreCaseSensitive(); 
    list.Distinct().Dump(); 
    list.Distinct(comparer).Dump(); 
} 
//自訂忽略大小寫的相等比較子 
public class IgnoreCaseSensitive : IEqualityComparer<string> 
{ 
    public bool Equals(string s1, string s2) 
    { 
        return (s1.ToUpper().Equals(s2.ToUpper())); 
    } 
    public int GetHashCode(string str) 
    { 
        return str.ToUpper().GetHashCode(); 
    } 
}


上述程式碼我們建立了一個廠牌名稱的字串清單,其中 ASUS 和 Acer 重覆出現,但是大小寫不一樣,接著設定兩個 LINQ 查詢,第一個查詢呼叫第一個多載方法,也就是用項目本身型別的預設相等比較子來判斷是否相等,因為字串預設相等比較子是區分大小寫,所以最後輸出結果會和來源序列一樣,都是 6 個項目;第二個 LINQ 查詢,我們使用 Distinct 第二個多載方法,傳入一個實做 IEqualityComparer<string> 的型別,可以使用不區分大小寫的方式來判斷字串是否相等,最後輸出結果只會留下四個項目,ASUS 和 Acer 都只留下一個,而且是索引最小的項目。


上一篇
LINQ自學筆記-語法應用-設定方法-Any、All、Contains
下一篇
LINQ自學筆記-語法應用-聚合資料-Join-1
系列文
分享一些學習心得30

尚未有邦友留言

立即登入留言