最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

第 97 講:C# 3 之查詢表達(dá)式(一):LINQ 的概念以及 from 和 select 關(guān)鍵字

2022-03-25 09:24 作者:SunnieShine  | 我要投稿

歡迎來(lái)到 LINQ 語(yǔ)法。LINQ 是 C# 里面最復(fù)雜的一個(gè)語(yǔ)法體系了,它的復(fù)雜程度估計(jì)就跟面向?qū)ο蟛畈惶?,是一個(gè)巨龐大的體系。下面我們將使用十多講來(lái)完成對(duì) LINQ 的介紹和全面講解。

今天我們要說(shuō)的是 LINQ 的基本概念,以及 LINQ 的簡(jiǎn)易使用。

Part 1 引例

在早期的 C# 里,很多東西使用起來(lái)都已經(jīng)比較方便了,但是有些時(shí)候,我們對(duì)集合的數(shù)據(jù)搜尋來(lái)說(shuō),仍舊有些不便??紤]一種情況。假設(shè)我要獲取一個(gè)數(shù)組里的所有奇數(shù),我們的寫(xiě)法是這樣的:

通過(guò) foreach 循環(huán),我們可以得到所有 array 里的奇數(shù)。當(dāng)然,現(xiàn)在我們有了擴(kuò)展方法,我們可以封裝一下:

封裝了之后,調(diào)用起來(lái)就簡(jiǎn)單多了:

我們使用傳入 Lambda 表達(dá)式的方式,來(lái)完成搜尋奇數(shù)的目的。這里我們用到了新語(yǔ)法有:

  • C# 2 的靜態(tài)類(lèi);

  • C# 2 的泛型;

  • C# 2 的 yield return 表達(dá)式;

  • C# 3 的擴(kuò)展方法;

  • C# 3 的 Lambda 表達(dá)式。

試想一下,Lambda 表達(dá)式是挺好用,但有沒(méi)有稍微優(yōu)雅一點(diǎn)的、專(zhuān)門(mén)用來(lái)搜索集合元素的語(yǔ)法來(lái)處理這個(gè)?答案是有的,下面我們就來(lái)說(shuō)一下,C# 3 的 LINQ。

通過(guò) C# 3 的 LINQ,我們可以這么寫(xiě)代碼:

是的,這個(gè) from e in array where e % 2 == 1 select e 是一個(gè)表達(dá)式。這個(gè)表達(dá)式有些長(zhǎng),不過(guò)挺有意思的。而至于 from、inselectwhere 等等關(guān)鍵字,是我們這里要說(shuō)的東西。

Part 2 LINQ 是什么?

LINQ,全稱(chēng) Language-Integrated Query,直接翻譯出來(lái)叫做“集成語(yǔ)言查詢”。這個(gè)是什么意思呢?查詢(Query)這個(gè)詞語(yǔ),在整個(gè) IT 界都小有名氣,它表示搜索我們需要的東西的行為。查詢是一個(gè)術(shù)語(yǔ)詞,大概可以理解為搜索的意思。

所謂的“集成語(yǔ)言查詢”,這個(gè)應(yīng)該理解為“語(yǔ)言集成的查詢機(jī)制”,這個(gè)“語(yǔ)言”指的是 C# 這個(gè)編程語(yǔ)言,而集成指的是在 C# 這門(mén)語(yǔ)言里有內(nèi)置語(yǔ)法,我們可以通過(guò)這樣的語(yǔ)法來(lái)完成查詢操作和機(jī)制。

C# 的 LINQ 包含如下的一些關(guān)鍵字:

  • from:用來(lái)表示迭代變量;

  • in:表示迭代的集合;

  • select:表示我們需要去讓什么變量作為返回;

  • into:表示繼續(xù)使用迭代結(jié)果;

  • where:表示條件;

  • orderby:表示排序集合;

  • ascending:表示集合升序排序;

  • descending:表示集合降序排序;

  • group:表示分組集合;

  • by:表示分組集合的依據(jù);

  • join:表示在集合配對(duì)和連接別的數(shù)據(jù);

  • on:表示 join 連接期間的條件;

  • equals:表示連接的相等的成員比較。

可以看出,關(guān)鍵字使用情況相當(dāng)多。正是因?yàn)檫@樣的復(fù)雜性,所以 LINQ 三言兩語(yǔ)肯定是說(shuō)不完的。今天我們要介紹的是前面四個(gè)關(guān)鍵字:from、in、selectinto 語(yǔ)句。

Part 3 from-in-select 查詢

3-1 語(yǔ)法

下面我們針對(duì)于集合迭代過(guò)程來(lái)簡(jiǎn)易說(shuō)明一下使用。先介紹的是最基礎(chǔ)的 from-in-select 語(yǔ)句。

from-in-select 語(yǔ)句,用連字符連起來(lái)的意思就是說(shuō),它們?nèi)齻€(gè)關(guān)鍵字是順次書(shū)寫(xiě)的,語(yǔ)法是這樣的:

我們把 from-in-select 語(yǔ)句稱(chēng)為映射表達(dá)式(Projection Expression)。舉個(gè)例子,我們要將集合里的所有元素都加一個(gè)單位,然后反饋出來(lái)的話,我們可以使用的寫(xiě)法是這樣:

我們直接在 select 從句的后面寫(xiě)上 element + 1 作為表達(dá)式,這表示我們獲取的元素是 element + 1 作為結(jié)果;而 fromin 這一部分則表示的是集合迭代的過(guò)程,書(shū)寫(xiě)的寫(xiě)法總是 from 后跟上迭代變量的名稱(chēng),而 in 后跟上的是集合變量。

這樣的語(yǔ)法看不習(xí)慣,可以使用 foreach 循環(huán)進(jìn)行等價(jià)轉(zhuǎn)換:

是的。就等價(jià)于這個(gè)。foreach (var element in array) 被代替為 from element in array,而 yield return element + 1 被代替為 select element + 1。因此,你可以說(shuō),from-in 部分表示 foreach 循環(huán)的意思,而 select 部分表示的是 yield return 的意思。

不過(guò),from-in-select 是不可分割的,也就是說(shuō)一旦出現(xiàn)就必須全都包含,不能缺少任何其中的一部分。比如說(shuō),只有 from-in 的語(yǔ)句是錯(cuò)誤的:

請(qǐng)注意返回值的類(lèi)型。我們?cè)诘葍r(jià)變回 foreach 循環(huán)后,這個(gè)方法的名稱(chēng) AddOne 返回的結(jié)果類(lèi)型是 IEnumerable<int>。是的,這一點(diǎn)需要你注意。因?yàn)榈葍r(jià)的轉(zhuǎn)換的關(guān)系,from-in-select 整個(gè)部分我們稱(chēng)為一個(gè)表達(dá)式(因?yàn)樗梢詫?xiě)在等號(hào)右邊,進(jìn)行賦值給左邊的變量),而這個(gè)表達(dá)式的結(jié)果類(lèi)型是 IEnumerable<> 類(lèi)型的。換句話說(shuō),實(shí)際上這里的 var 就表示 IEnumerable<int>

不過(guò),有些時(shí)候比較長(zhǎng),因此你可以換行:

都是可以的。不過(guò)我們建議你換行的時(shí)候?qū)?fromselect 單獨(dú)作為一行,而不要斷開(kāi) fromin,因?yàn)樗鼈儽坏葍r(jià)為 foreach 循環(huán)的聲明的頭部了,它們是不可拆分的。

我們把 from element in array select element + 1 稱(chēng)為一個(gè)查詢表達(dá)式(Query Expression),它是一個(gè)表達(dá)式,而且功能是用來(lái)查詢,因此叫做查詢表達(dá)式;另外,查詢表達(dá)式不只是 from-in-select 表達(dá)式,還有別的,后面我們會(huì)慢慢接觸到它們。

另外,我們把 from 后跟的變量也稱(chēng)為迭代變量(Iteration Variable),不過(guò)在 C# 里,它只在這個(gè)表達(dá)式里才能使用,比大括號(hào)的級(jí)別還要小,因此我們把這樣的變量也稱(chēng)為范圍變量(Range Variable)。

3-2 select 從句就用迭代變量的情況

如果使用 select 的時(shí)候,我們只寫(xiě)本身的話,會(huì)如何呢:

from 里聲明的 element 變量直接寫(xiě)在 select 后了。如果把它轉(zhuǎn)換為 foreach 循環(huán)的話,是這樣的:

這不是多此一舉嗎?我故意使用 foreach 將數(shù)組的每一個(gè)元素都迭代出來(lái),結(jié)果我又使用 yield return 把每一個(gè) element 給整合起來(lái)。這種寫(xiě)法也不是錯(cuò)的,不過(guò)沒(méi)有必要這么寫(xiě),對(duì)吧。

3-3 select 從句用常量的情況

考慮下面的查詢表達(dá)式:

請(qǐng)問(wèn),這么寫(xiě)有意義嗎?沒(méi)有。因?yàn)?element 沒(méi)有用到,而每一個(gè)迭代操作最終都反饋了 42 這個(gè)常量出去,因此它等價(jià)于這樣的代碼:

因此,我們也盡量避免這種寫(xiě)法。真要說(shuō)這個(gè)迭代的結(jié)果是什么,那就只能表示成“和集合元素?cái)?shù)量一樣多的 42 構(gòu)成的序列”。

3-4 使用查詢表達(dá)式需要引用 System.Linq 命名空間

在使用上述這樣的查詢表達(dá)式而不是 foreach 循環(huán)的時(shí)候,我們需要在文件最開(kāi)頭補(bǔ)充 using System.Linq; 命名空間的引用,原因今天講不了,這個(gè)我們將在后面說(shuō)到。

Part 4 顯式指定迭代變量的類(lèi)型

可以從前文給出的語(yǔ)法規(guī)則看出,映射表達(dá)式的 from 后是有一個(gè)變量類(lèi)型可以寫(xiě)的,不過(guò)它被標(biāo)記了 ? 說(shuō)明可以沒(méi)有。前面講的是沒(méi)有的情況,下面我們來(lái)說(shuō)一下帶有變量類(lèi)型的情況。

考慮使用 ArrayList 這樣的集合。如果這樣的代碼在迭代的時(shí)候,將會(huì)產(chǎn)生錯(cuò)誤:

但是,這樣書(shū)寫(xiě)有一個(gè)問(wèn)題。ArrayList 類(lèi)型是很早的數(shù)據(jù)類(lèi)型,它雖然是一個(gè)集合,但里面的元素是 object 類(lèi)型的,雖然我們知道,我們這個(gè)集合里只存 int 元素,但因?yàn)樗陨碇粚?shí)現(xiàn)了 IEnumerable 接口,而沒(méi)有實(shí)現(xiàn)泛型的版本(它自己在沒(méi)有泛型之前就有了),因此 from-in 在書(shū)寫(xiě)的時(shí)候就會(huì)失敗。所以,上面的代碼是不合理的。

你想想,你轉(zhuǎn)換為 foreach 后,代碼是不是這樣:

e 是什么類(lèi)型呢?object,對(duì)吧:

可是,e + 1 就不對(duì)了。于是,我們會(huì)試著改變 object

我們通過(guò)顯式指定數(shù)據(jù)類(lèi)型 int 來(lái)代替 object,這樣我們就可以在 foreach 的底層自動(dòng)進(jìn)行 objectint 的強(qiáng)制轉(zhuǎn)換,因?yàn)槲覀冎烂恳粋€(gè)元素都是 int 類(lèi)型,所以這么寫(xiě)是可以的。于是,這樣的聲明是合理的。

但仔細(xì)想想,前面我們介紹的例子迭代的是 int[],也就是說(shuō),foreach 循環(huán)我們完全不必聲明它迭代變量的類(lèi)型,寫(xiě)成 var 編譯器也知道;但是 ArrayList 不行,不寫(xiě)就不知道具體類(lèi)型,因此我們必須強(qiáng)制寫(xiě)出來(lái)元素自己的實(shí)際類(lèi)型,然后做一次隱式的強(qiáng)制轉(zhuǎn)換,是的,隱式的強(qiáng)制轉(zhuǎn)換。隱式指的是它在底層才知道有這個(gè)轉(zhuǎn)換邏輯,而直接看 foreach 是看不太出來(lái)的;而強(qiáng)制轉(zhuǎn)換是背后執(zhí)行的邏輯和機(jī)制。

那么,LINQ 里要怎么做呢?from e in list select e + 1 嗎?肯定不行。于是 LINQ 允許我們?cè)诘兞康淖筮吪渖纤膶?shí)際類(lèi)型,表示進(jìn)行強(qiáng)制轉(zhuǎn)換的具體類(lèi)型。于是,改寫(xiě)為這樣就可以了:

是的,from int e in list。

Part 5 疊加 from-in 從句

from-in 從句是可以疊加的??紤]一下,我有兩個(gè)集合需要迭代,然后湊一對(duì),我們可以這么做:

我們可以構(gòu)成一個(gè)數(shù)對(duì),然后用匿名類(lèi)型表示出來(lái),然后兩層循環(huán),將兩個(gè)集合的元素兩兩組合。C# 的 LINQ 也能做這個(gè)事情:

是的。我們只需要疊加起來(lái)即可。我們使用兩層 from-in 從句,就可以達(dá)到兩層循環(huán)的效果。這就是 LINQ 的魅力之處。當(dāng)然,from-in 并未要求必須最多幾個(gè),實(shí)際上你可以繼續(xù)疊加:

是的,匿名類(lèi)型是不用寫(xiě)出來(lái)屬性名稱(chēng)的,C# 的匿名類(lèi)型具有屬性名推斷的功能,如果你寫(xiě)的匿名類(lèi)型的表達(dá)式是 new { a, b } 的話,那么生成的對(duì)應(yīng)匿名類(lèi)型的具體類(lèi)型,會(huì)直接使用變量名稱(chēng) ab 表示這兩個(gè)實(shí)際屬性,而如果是 new { 1 } 就不行了,因?yàn)?1 是常量,它沒(méi)有對(duì)應(yīng)的變量名稱(chēng),因此無(wú)法將其當(dāng)作屬性名使用,這種情況下就必須寫(xiě)屬性名稱(chēng)了。

另請(qǐng)注意 new[] { a, b }new { a, b } 的區(qū)別。new[] { a, b } 是數(shù)組的隱式類(lèi)型的初始化器,因?yàn)橛袀€(gè)方括號(hào);而 new { a, b } 沒(méi)有方括號(hào)了,因此會(huì)被視為匿名類(lèi)型的表達(dá)式。

Part 6 嵌套查詢和 into 從句

有些時(shí)候,foreach 僅僅是上面那樣的話,就顯得比較簡(jiǎn)單了,一些復(fù)雜的東西可能前面的內(nèi)容就做不到了。下面我們來(lái)看一些靈活的處理。

6-1 嵌套查詢

考慮一種情況,我們要通過(guò) from-in-select 表達(dá)式得到一個(gè)新集合,然后將這個(gè)集合再一次使用和映射起來(lái)。我們可以這么做:

是的,我們迭代 c1 集合,得到每一個(gè)元素的平方根,然后組成新的序列;與此同時(shí)我們迭代 c2 集合,得到這個(gè)序列每一個(gè)元素的平方根。最后,我們使用疊加的 from-in 從句,來(lái)進(jìn)行兩兩配對(duì),最后得到每一組兩個(gè)元素的平方根的和。

這里稍微提一句。注意 temp1temp2 的查詢表達(dá)式寫(xiě)法。在 from 后,兩個(gè)查詢表達(dá)式用的是相同的標(biāo)識(shí)符 t,但是因?yàn)榍拔恼f(shuō)過(guò),查詢表達(dá)式內(nèi)的變量我們叫迭代變量,它僅用于轉(zhuǎn)化為 foreach 循環(huán)后的這個(gè)循環(huán)體內(nèi)部使用,因此在查詢表達(dá)式里,迭代變量?jī)H可以提供給整個(gè)查詢表達(dá)式的范圍使用,超出這個(gè)范圍的任何其它的地方都是無(wú)法“看”到它的。因此,兩個(gè) t 并不會(huì)沖突,正是因?yàn)槿绱?,我們才叫它范圍變量,因?yàn)樗却罄ㄌ?hào)的級(jí)別還要小,只在表達(dá)式的小范圍里才能隨便使用它。

這樣的寫(xiě)法是可以,不過(guò) temp1temp2 不必顯式用變量寫(xiě)出來(lái),于是我們可以內(nèi)聯(lián)到同一個(gè)語(yǔ)句里去。

想一想,from-inin 后面是不是就是集合?。恳胧且粋€(gè)集合,首先是不是得至少可以允許它進(jìn)行 foreach 循環(huán)???有了這個(gè)循環(huán)我們才能將查詢表達(dá)式和 foreach 進(jìn)行等價(jià)轉(zhuǎn)換。那么至少這個(gè)集合就得是 IEnumerable<> 或者是 IEnumerable 的。欸,等下,是不是很熟悉?是的,查詢表達(dá)式自己的結(jié)果就是這兩個(gè)接口類(lèi)型的。那如果我直接將結(jié)果內(nèi)聯(lián)寫(xiě)到 in 的后面不就可以了?

是的。LINQ 的靈活遠(yuǎn)超我們預(yù)期。查詢表達(dá)式允許我們這么寫(xiě)代碼。甚至編譯器知道 in 后面的是一個(gè)單獨(dú)的查詢表達(dá)式,它甚至允許我們?nèi)サ粜±ㄌ?hào):

這樣也是可以的。

我們把第 2、3 行的 in 關(guān)鍵字后的查詢表達(dá)式稱(chēng)為嵌套查詢表達(dá)式(Nested Query Expression),它嵌套在整個(gè) from-in-from-in-select 表達(dá)式的里面。不過(guò)注意使用和計(jì)算順序,先計(jì)算出內(nèi)層的結(jié)果,然后才能執(zhí)行和得到整個(gè) from-in-from-in-select 表達(dá)式的結(jié)果。

6-2 into 從句

我們可以使用 select-into 從句來(lái)完成對(duì)臨時(shí)查找的結(jié)果繼續(xù)進(jìn)行迭代操作的“接續(xù)”。考慮一下前面的代碼,我們發(fā)現(xiàn)一個(gè)通用的結(jié)論:select 總是被放在末尾。因?yàn)?select 用來(lái)映射結(jié)果,因此它肯定只能放末尾;但有些時(shí)候也不一定。

考慮一種情況,我給出了一系列的學(xué)生信息:

我給出了假機(jī)器人鷺宮詩(shī)織、真機(jī)器人東云名乃、蠟筆小新、湯姆貓、杰瑞鼠、托爾醬、康娜醬、下弦之一魘夢(mèng)、平澤唯和阿梓喵這幾個(gè)學(xué)生的基本信息(說(shuō)這段好像沒(méi)啥意思,但是我就是故意說(shuō)的,我二次元濃度真的不高)。

我想將列表的所有學(xué)生都看一遍,看看是不是都成年了,然后給每一個(gè)學(xué)生都配上結(jié)果。我不管你是不是來(lái)自異次元,咱只看地球上的標(biāo)準(zhǔn)——看這些學(xué)生是不是滿 18 歲。那么代碼應(yīng)該是這樣:

是的,這里我們使用到了一個(gè)新的語(yǔ)法:select-into 從句。請(qǐng)注意第 3 行。代碼使用到的語(yǔ)句寫(xiě)法是 select new { ... } into ...,這表示什么呢?這表示的是,我將前面的學(xué)生的信息,按 new { ... } 這個(gè)匿名類(lèi)型的表達(dá)式轉(zhuǎn)換一下,然后將轉(zhuǎn)換的這個(gè)結(jié)果,使用 temp 表示成變量。

換句話說(shuō),select-into 語(yǔ)句允許我們定義臨時(shí)變量在中間。它將 select 后的表達(dá)式進(jìn)行中間化,避免 select 一定要放在最后的問(wèn)題。

當(dāng)然了,上述這樣的代碼也可以被精簡(jiǎn)一下:

是的,這樣寫(xiě)其實(shí)也沒(méi)毛病,只是說(shuō)初學(xué)的話可能不太好理解。

6-3 “副作用”:into 從句會(huì)阻斷范圍變量的使用范圍

這是一個(gè)比較“奇怪”的設(shè)計(jì)??赡苣銜?huì)覺(jué)得是這樣,不過(guò) select-into 設(shè)計(jì)出來(lái)是別有用途的,但目前我們還沒(méi)辦法說(shuō),下一講內(nèi)容我們會(huì)講解 let 關(guān)鍵字,到時(shí)候我們就會(huì)說(shuō)明 select-into 這種副作用的真正原因。

比如下面這樣寫(xiě)代碼,就是不對(duì)的:

new { digit, d1 } 是不對(duì)的寫(xiě)法,因?yàn)槭褂?d1 變量是不可以的,因?yàn)榍懊嬗?select-into 從句,這意味著 into digit 之前定義的變量就不能在 into digit 之后使用了。

Part 7 查詢表達(dá)式當(dāng)成參數(shù)和臨時(shí)表達(dá)式使用

前面我們就說(shuō)過(guò),查詢表達(dá)式是一個(gè)表達(dá)式,它是 IEnumerable<> 類(lèi)型的。因此,任何可以用表達(dá)式的地方,只要類(lèi)型對(duì)得上,這樣的語(yǔ)法就可以無(wú)處不在。

比如 string.Join 方法。這個(gè)方法用來(lái)給序列的元素和元素之間插入分隔符。假設(shè)我有一組元素,要在每個(gè)元素之間插入逗號(hào),我們可以這么寫(xiě)代碼:

是的,我們直接將查詢表達(dá)式當(dāng)成參數(shù)傳入到第二個(gè)位置上去。這是允許的寫(xiě)法。

另外,我們也可以使用前面講過(guò)的擴(kuò)展方法來(lái)拓展用法。假設(shè)我想要獲取整個(gè)集合的第一個(gè)元素的話:

我們這里有了擴(kuò)展方法了。下面我們就可以這么做了:

我們使用查詢表達(dá)式得到所有學(xué)生的名字,然后使用 First 擴(kuò)展方法的語(yǔ)法來(lái)獲取這個(gè)序列里的第一個(gè)元素。因此這個(gè)整個(gè)計(jì)算過(guò)程的結(jié)果,其實(shí)就是名字序列里的第一個(gè),因此 s 就是這個(gè)名字,是 string 類(lèi)型的。

稍微注意一下的是,由于查詢表達(dá)式的完整性和復(fù)雜性,我們必須在調(diào)用擴(kuò)展方法之前,使用小括號(hào)把整個(gè)查詢表達(dá)式給括起來(lái),這是在這種用法下唯一需要我們注意的地方。

Part 8 總結(jié)

今天我們講解了使用 from、inselect 關(guān)鍵字來(lái)完成一些操作,知道了什么叫做查詢表達(dá)式,范圍變量的具體范圍又是哪些。另外,我們?cè)谶@一節(jié)里大量使用到匿名類(lèi)型,也對(duì)我們之前學(xué)習(xí)匿名類(lèi)型來(lái)說(shuō)有了一個(gè)比較合理的認(rèn)知。

下一節(jié)我們將給大家介紹 let 從句,用來(lái)臨時(shí)定義變量。


第 97 講:C# 3 之查詢表達(dá)式(一):LINQ 的概念以及 from 和 select 關(guān)鍵字的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
渭南市| 江川县| 四会市| 五大连池市| 宜城市| 文化| 锡林郭勒盟| 大埔县| 巴青县| 怀集县| 西青区| 桂平市| 凭祥市| 高要市| 兰州市| 宜阳县| 许昌市| 潢川县| 修武县| 华安县| 吉木萨尔县| 自治县| 靖西县| 岳阳市| 平遥县| 阿克陶县| 南城县| 仪陇县| 五莲县| 佳木斯市| 黄石市| 青海省| 嘉定区| 宾阳县| 肇东市| 武汉市| 深水埗区| 瑞金市| 漳州市| 荥阳市| 延长县|