Select的應用
前面累積了這麼多的觀念,現在終於要用上了,我們要來正式介紹第一個LINQ語法Select
了。
格式說明
在應用的單元裡文章會被分為幾個小節:
- 功能說明: 語法的功用
- 方法定義: 語法的方法定義
- 查詢運算式: 此語法的運算是使用方法
- 方法範例: 使用各個方法實作例子
- 特別之處: 語法的特性說明
因應不同的語法章節內容會有微調。
功能說明
Select
運算子可以將集合中的每一個元素以新的形式輸出,其用法與SQL的Select
相似。
方法定義
Select有兩個Public的方法如下:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector)
Select
只有一個傳入參數Selector
,這個參數是一個委派參數,而傳入的方法其實就是你想要對每一個元素什麼樣的事情。
兩種方法的差別在於第二個委派參數Selector
,兩個委派方法差了一個int
傳入參數,這個int
其實就是集合的index
,工程師可以利用這個傳入參數取得目前元素在集合中的位置。
查詢運算式
依照C# Spec上的語法定義如下:
select_clause
: 'select' expression
;
expression
: 在每個元素上你要取得的資料物件
看定義很難懂對不對? 我們先來看個例子吧:
from x in Products
select x.ProductName
- 第一行的
from
是將產品(Products
)這個集合上的每一個元素以x
來表示 - 第二行的
select
代表說要將每一個元素(x
)的產品名稱(ProductName
)輸出
當有多個資料想要藉由select
輸出時,也可以用Anonymous types和Object Initializer來達到此目的:
from x in Products
select new {x.ProductName, x.UnitPrice}
看到這裡我們知道expression
其實就是一個變數或是一個類別,而這個變數(或類別)就是你期望在每個元素上取得的資料。
例子
看過前一章的介紹可以知道,其實查詢運算式會轉換成標準查詢運算子,現在我們就用上節的查詢運算式例子轉換成標準查詢運算子看看:
// Using query expression syntax.
var query = from x in Products
select new {x.ProductName, x.UnitPrice};
// Using method-based query syntax.
var method = Products
.Select (
x =>
new
{
ProductName = x.ProductName,
UnitPrice = x.UnitPrice
}
);
在這個例子的比對中我們可以發現:
- Query的
from
的目的就是要確定資料來源及其在Lambda運算式中的別名 - Query的
Select
後面接的其實就是Lambda expression
上面的例子沒有使用到index
這個參數,現在我們來看個有index
的例子:
string[] data = { "No1", "No2", "No3", "No4", "No5"};
var query =
data.Select((item, index) => new { index, item });
foreach (var obj in query)
{
Console.WriteLine("{0}", obj);
}
/*
* { index = 0, item = No1 }
* { index = 1, item = No2 }
* { index = 2, item = No3 }
* { index = 3, item = No4 }
* { index = 4, item = No5 }
*/
我們可以看到這個index
的資訊也會進入每一個元素中,對於需要知道元素位置的處理很有幫助。
特別之處
Select這個看似平凡的語法其實也藏有一些玄機及特色,接下來我們就來看看吧。
Select效率超高?
下面這個例子讓各位猜猜會不會跑很久:
IEnumerable<int> afterSelect =
Enumerable.Range(1, 1000000000).Select(x => x * 2);
這例子是產生一個1到1000000000的數列,每個元素輸出兩倍的數字。
如果認為會跑很慢的人,沒關係,這邊再給一個提示: 接下來對afterSelect
做foreach
的花費是多還是少呢?
foreach(var result in afterSelect){}
下面是比較結果:
Select少了很多對吧,其實Select它有延遲執行的特性,意思是說你叫用它時,它不會馬上去巡覽所有的元素,而是會等到你叫用GetEnumerator()
或是foreach
時才會去變動集合,詳細的原理我們留到之後再深入討論。
Select是所有查詢都必備的語法嗎?
記得以前在看LINQ的相關介紹時,第一章出現的幾乎都是Select,所以我一直以為它是一個Query Expressions的基本,就像是from
一樣,你是不是也跟我的想法一樣呢? 如果是的話請繼續往下看。
現在我們來想想: 一段查詢語法它的基本要件是什麼? 是什麼可以讓這段文字產生查詢的能力? 首先我們會想到的是選取資料來源,沒有資料來源根本也不用做查詢了,那有了資料來源後還欠缺什麼呢? 最直覺的就是目標資料的結構了,所以Select
它是一個最直覺的查詢功能,從SQL的查詢與法也可以看出來。
所以Select幾乎都會在文章的開頭出現的謎底揭開了,因為人習慣以最直覺的開始講起,這樣的講述方式會讓人比較快熟悉並且進入狀況,所以Select
自然變成每個作者筆下的第一頭牌了。
那另一個問題浮現了: 它是一個Query Expressions的必備語法嗎? 其實Select在LINQ的語法中其實不一定需要出現,讓我們來看看下面的例子:
from x in Products
group x by x.CategoryID
這裡請了group
出來代班一下,我們可以看到這裡並沒有select,但這還是一個正確的查詢運算式,其實在C#語言規格中在介紹Query Experssions的章節有提到下面這段解釋:
A query expression begins with a
from
clause and ends with either aselect
orgroup
clause.
所以我們的一段最基本的查詢運算式會以from
開頭,select
或group
結尾,是不是顛覆了以往的想像呢?
上面的定義是在說查詢運算式,在標準查詢運算子上的規則又更為寬鬆了,因為它本身的回傳值是IEnumerable
所以你只要是對其做事的方法都可以使用,也不用一定要先執行某個方法才能做查詢。
結語
這章講述了Select的Query及Method的用法,在下一章學習如何建置corefx後就可以來看看Select
內到底在做什麼了。