本文將為大家介紹 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 都只留下一個,而且是索引最小的項目。