LINQ之路15:LINQ Operators之元素運算符、集合方法、量詞方法
本篇繼續LINQ Operators的介紹,包括元素運算符/Element Operators、集合方法/Aggregation、量詞/Quantifiers Methods。元素運算符從一個sequence當中獲取單個元素;集合方法對sequence進行統計/匯總并返回當個標量值;量詞方法用于判斷sequence是否滿足特定條件并返回bool值。
元素運算符/Element Operators
IEnumerable<TSource>→TSource
|
Operator |
說明 |
SQL語義 |
|
First, FirstOrDefault |
返回sequence中(可選滿足某個條件)的第一個元素 |
SELECT TOP 1 ... ORDER BY ... |
|
Last, LastOrDefault |
返回sequence中(可選滿足某個條件)的最后一個元素 |
SELECT TOP 1 ... ORDER BY ... DESC |
|
Single, SingleOrDefault |
相當于First/FirstOrDefault,但是如果不止一個匹配元素則拋出異常 |
|
|
ElementAt, ElementAtOrDefault |
返回特定位置上的元素 |
Exception thrown |
|
DefaultIfEmpty |
如何sequence沒有元素則返回null或default(TSource) |
OUTER JOIN |
以 “OrDefault”結束的方法在輸入sequence為空或沒有匹配的元素時返回default(TSource),而不是拋出異常。default(TSource)對于引用類型的元素來說等于null,對于值類型元素則通常等于0。
First, Last, and Single
|
參數 |
類型 |
|
源sequence |
IEnumerable<TSource> |
|
條件(可選) |
TSource => bool |
下面的例子示范了First和Last的方法:
int[] numbers = { 1, 2, 3, 4, 5 };
int first = numbers.First(); // 1
int last = numbers.Last(); // 5
int firstEven = numbers.First(n => n % 2 == 0); // 2
int lastEven = numbers.Last(n => n % 2 == 0); // 4
下面的例子對First和FirstOrDefault進行了對比:
int firstBigError = numbers.First(n => n > 10); // Exception
int firstBigNumber = numbers.FirstOrDefault(n => n > 10); // 0
對于Single運算符來說,必須只能有且僅有一個匹配元素,否則將會拋出異常;SingleOrDefault可以有一個或零個匹配元素:
int onlyDivBy3 = numbers.Single(n => n % 3 == 0); // 3
int divBy2Err = numbers.Single(n => n % 2 == 0); // Error: 2 & 4 match
int singleError = numbers.Single(n => n > 10); // Error
int noMatches = numbers.SingleOrDefault(n => n > 10); // 0
int divBy2Error = numbers.SingleOrDefault(n => n % 2 == 0); // Error
Single是上面這個家族中最“嚴格”的元素運算符,而FirstOrDefault和LastOrDefault則是最寬松的。
在LINQ to SQL和EF,Single經常用來在一個表中根據主鍵獲取一行數據:
Customer cust = dataContext.Customers.Single(c => c.ID == 3);
ElementAt
|
參數 |
類型 |
|
源sequence |
IEnumerable<TSource> |
|
待返回元素的索引 |
int |
ElementAt獲取sequence中特定位置上的元素:
int[] numbers = { 1, 2, 3, 4, 5 };
int third = numbers.ElementAt(2); // 3
int tenthError = numbers.ElementAt(9); // Exception
int tenth = numbers.ElementAtOrDefault(9); // 0
DefaultIfEmpty
DefaultIfEmpty把一個空sequence轉換為null/default()。它用來書寫平展的outer join,請參考:LINQ之路12:LINQ Operators之數據轉換(Projecting)中的SelectMany中的Outer joins一節。
集合方法/Aggregation Methods
IEnumerable<TSource>→ scalar
|
Operator |
說明 |
SQL語義 |
|
Count, LongCount |
返回輸入sequence中的元素個數,可以指定某個條件 |
COUNT (...) |
|
Min, Max |
返回輸入sequence中的最小/最大元素 |
MIN (...), MAX (...) |
|
Sum, Average |
對sequence中的元素求和或平均值 |
SUM (...), AVG (...) |
|
Aggregate |
執行定制的計算 |
拋出異常 |
Count 和LongCount
|
參數 |
類型 |
|
源sequence |
IEnumerable<TSource> |
|
條件(可選) |
TSource => bool |
Count簡單地遍歷某個sequence,返回其元素個數:
int fullCount = new int[] { 5, 6, 7 }.Count(); // 3
Enumerable.Count的內部實現會判斷輸入sequence是否實現了ICollection<T>接口,如果是,則調用ICollection<T>.Count,否則遍歷sequence中的每個元素并進行計數。
我們可以選擇提供一個條件:
int digitCount = "pa55w0rd".Count(c => char.IsDigit(c)); // 3
LongCount的功能與Count一樣,但是返回64-bit整數,這樣就允許遍歷元素超過32-bit整數范圍的sequence。
Min 和Max
|
參數 |
類型 |
|
源sequence |
IEnumerable<TSource> |
|
結果選擇器(可選) |
TSource => TResult |
Min和Max返回輸入sequence中的最小/最大元素:
int[] numbers = { 28, 32, 14 };
int smallest = numbers.Min(); // 14;
int largest = numbers.Max(); // 32;
如果我們提供了選擇器表達式,每個元素都會先進行數據轉換:
int smallest = numbers.Max(n => n % 10); // 8;
如果元素本質上不支持比較,那么選擇器表達式就是必須的。換句話說,如果他們沒有實現IComparable<T>:
Purchase runtimeError = dataContext.Purchases.Min(); // Error
decimal? lowestPrice = dataContext.Purchases.Min(p => p.Price); // OK
選擇器表達式不只是決定了元素的比較方式,而且決定了最終的結果。上面使用p => p.Price的示例中,最終結果是一個decimal數值,而不是purchase對象。如果要想得到最便宜的purchase,我們需要一個子查詢:
Purchase cheapest = dataContext.Purchases
.Where(p => p.Price == dataContext.Purchases.Min(p2 => p2.Price))
.FirstOrDefault();
當然上面這個例子中,我們也可以不使用Min,而用OrderBy來獲得相同的結果。
Sum和Average
|
參數 |
類型 |
|
源sequence |
IEnumerable<TSource> |
|
結果選擇器(可選) |
TSource => TResult |
Sum和 Average的使用方式和Min/Max類似,他們用于對sequence中的元素求和或平均值:
decimal[] numbers = { 3, 4, 8 };
decimal sumTotal = numbers.Sum(); // 15
decimal average = numbers.Average(); // 5
下面的查詢返回names數組中每個字符串的總長度:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
int combinedLength = names.Sum(s => s.Length); // 19
Sum和Average對他們操作的元素類型相當嚴格,他們可以操作下面這些數值類型(int, long, float, double, decimal, 和他們的可空版本)。而Min和Max可以直接操作任何實現了IComparable<T>的對象,如string。并且, Average總是參照下表返回decimal或者double類型的結果:
|
選擇器類型 |
結果類型 |
|
decimal |
decimal |
|
int, long, float, double |
double |
這意味著下面的查詢無法編譯 (“cannot convert double to int”):
// cannot convert double to int
int avg = new int[] { 3, 4 }.Average();
// But this will compile:
double avg2 = new int[] { 3, 4 }.Average(); // 3.5
為了防止丟失數據精度,Average隱式地把輸入數值轉換到更寬的數據類型。
當查詢數據庫時,Sum和Average會被翻譯到標準的SQL 集合運算符。下面的查詢返回purchase平均值超過500的Customers:
var query = from c in dataContext.Customers
where c.Purchases.Average (p => p.Price) > 500
select c.Name;
Aggregate
Aggregate允許我們使用定制的算法來完成不常見的匯總/計算。Aggregate在LINQ to SQL和Entity Framework中不被支持,并且它的使用方法有些特殊。下面的示例用Aggregate完成了Sum的功能:
int[] numbers = { 2, 3, 4 };
int sum = numbers.Aggregate(0, (total, n) => total + n); // 9
Aggregate的第一個參數是算法的種子,即初始值。第二個參數是一個表達式,用來針對每個元素更新計算數值。我們可以選擇提供第三個參數來對最終結果進行數據轉換。
Aggregate可以解決的很多問題同樣可以使用foreach循環(更熟悉的語法形式)來完成。使用Aggregate的優勢是對于復雜的計算,我們可以使用PLINQ自動實現平行的計算任務。
不帶種子的集合計算
當調用Aggregate時,我們可以省略種子值,這時第一個元素會隱式成為種子值,并且集合計算從第二個元素繼續下去。
// 省去種子參數調用Aggregate
int[] numbers = { 1, 2, 3 };
int sum = numbers.Aggregate ((total, n) => total + n); // 6
這個例子完成和上面例子同樣的功能(求和),但是實際上我們完成了兩個不同的計算。前一個例子,我們計算0+1+2+3;現在我們計算的是1+2+3。我們可以使用乘法來更好的展示他們之間的區別:
int[] numbers = { 1, 2, 3 };
int x = numbers.Aggregate(0, (prod, n) => prod * n); // 0*1*2*3 = 0
int y = numbers.Aggregate((prod, n) => prod * n); // 1*2*3 = 6
量詞/Quantifiers
IEnumerable<TSource>→bool
|
Operator |
說明 |
SQL語義 |
|
Contains |
如果輸入sequence包含給定的element則返回true |
WHERE ... IN (...) |
|
Any |
如果任意一個元素滿足給定條件則返回true |
WHERE ... IN (...) |
|
All |
如果所有元素都滿足給定條件則返回true |
WHERE (...) |
|
SequenceEqual |
如果第二個sequence和輸入sequence擁有相同的elements則返回true |
|
Contains 和Any
Contains方法接受一個TSource類型參數;Any可選地接受一個條件表達式。
如果輸入sequence包含給定的element,Contains返回true:
bool hasAThree = new int[] { 2, 3, 4 }.Contains(3); // true;
如果任意一個元素滿足給定條件,Any返回true。我們可以使用Any來重寫上面的查詢:
bool hasAThree = new int[] { 2, 3, 4 }.Any(n => n == 3); // true;
Any可以完成Contains可以完成的任何事情,也能完成Contains不能完成的事情:
bool hasABigNumber = new int[] { 2, 3, 4 }.Any(n => n > 10); // false;
如果調用Any時省略了條件表達式,則只要sequence中含有元素就返回true,下面使用另一種方式完成了上面查詢的功能:
bool hasABigNumber = new int[] { 2, 3, 4 }.Where(n => n > 10).Any();
Any在我們使用子查詢時非常有用,并且經常用在數據庫查詢中,比如:
var query =
from c in dataContext.Customers
where c.Purchases.Any(p => p.Price > 1000)
select c;
All 和SequenceEqual
如果所有的元素都符合給定條件,則All返回true。下面的查詢返回所有purchases都小于100的customers:
var query =
dataContext.Customers.Where(c => c.Purchases.All(p => p.Price < 100));
SequenceEqual比較兩個sequence。如果他們擁有相同的元素,且相同的順序,則返回true:
int[] numbers1 = { 2, 3, 4 };
int[] numbers2 = { 2, 3, 4 };
int[] numbers3 = { 3, 2, 4 };
Console.WriteLine(numbers1.SequenceEqual(numbers2)); // True
Console.WriteLine(numbers1.SequenceEqual(numbers3)); // False

浙公網安備 33010602011771號