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

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

第 102 講:C# 3 之查詢表達(dá)式(六):join、on 和 equals 關(guān)鍵字

2022-04-14 09:52 作者:SunnieShine  | 我要投稿

今天我們來看新的從句類型:join 系列從句。這個(gè)從句放在最后講解是因?yàn)樗铍y,用法比較多,而且在處理機(jī)制上相對(duì)比較復(fù)雜。

Part 1 引例:下弦之鬼和寵物的對(duì)應(yīng)關(guān)系

假設(shè)你看過動(dòng)漫《鬼滅之刃》。我們這里定義一種數(shù)據(jù)類型叫 Demon,存儲(chǔ)的是鬼的名字:

這里我們使用 private set 的原因是,C# 3 不允許只有 get 的自動(dòng)屬性,因此我們補(bǔ)充了一個(gè)私有化了的 setter 防止被外界調(diào)用,也可以簡(jiǎn)化代碼避免寫全字段。

接著,我們定義六個(gè)下弦鬼以及對(duì)應(yīng)的信息:

lowerOnelowerSix 分別對(duì)應(yīng)下弦之一到下弦之六:魘夢(mèng)、轆轤、病葉、零余子、累和釜鵺。

圖片來自萌娘百科

然后,我們假設(shè)給它們定義一些寵物。

當(dāng)然,有的鬼沒有寵物,有些鬼可以包含多個(gè)寵物,假設(shè)我們有這么一些寵物:

我們?cè)诔跏蓟臅r(shí)候都給每一個(gè)寵物配上了對(duì)應(yīng)的名字和它的主人。我們暫時(shí)不考慮寵物是貓是狗還是什么其它的動(dòng)物類型。

現(xiàn)在我們想要使用查詢表達(dá)式獲取所有寵物的對(duì)應(yīng)主人,然后構(gòu)成一個(gè)映射關(guān)系,將它們表示出來。這個(gè)怎么做呢?

最好想到的辦法就是使用交叉連接(Cross Join)。交叉連接也叫笛卡爾積(Cartesian Product),指的是兩個(gè)序列,第一個(gè)序列的每一個(gè)元素都和第二個(gè)序列的每一個(gè)元素一一配對(duì)的行為。在 LINQ 里面,則對(duì)應(yīng)的是兩個(gè)挨著的 from-in 從句。因?yàn)閮蓚€(gè)連續(xù)的 from-in 從句在翻譯成 foreach 的等價(jià)代碼的時(shí)候,是嵌套的,嵌套的循環(huán)就是外層的一個(gè)元素對(duì)應(yīng)內(nèi)層的一組元素,畢竟,內(nèi)層循環(huán)的所有元素都得遍歷完之后,外層循環(huán)才會(huì)繼續(xù)迭代下一個(gè)元素,這恰好是笛卡爾積的基本用法和定義。

我們使用 from-from-where-select 的從句序列來表示笛卡爾積和匹配。第 4 行代碼使用一次 where 從句可以篩選和計(jì)算是否當(dāng)前 pet 對(duì)象的 Owner 屬性(主人)是當(dāng)前迭代的 demon 對(duì)象。

注意中間我們用的是 == 運(yùn)算符。因?yàn)槭且妙愋?,而我們?duì) Demon 數(shù)據(jù)類型并未涉及任何的比較操作的重載行為,所以這個(gè) == 是在比較兩個(gè)對(duì)象的引用是否一致。它等價(jià)于在調(diào)用 ReferenceEquals 方法。顯然,我們?cè)诖a里面,確實(shí)給每一個(gè)寵物的主人傳參的都是下弦之鬼的實(shí)例引用,因此這么去比較沒有任何問題。

接著,我們最后使用 select 從句將寵物名稱和對(duì)應(yīng)主人的名稱使用匿名類型映射出來。最后得到的就是所有一一匹配的結(jié)果了。

仔細(xì)琢磨一下,這個(gè)查詢表達(dá)式是否嚴(yán)謹(jǐn),會(huì)不會(huì)多出冗余情況,也會(huì)不會(huì)漏掉情況。其實(shí)是不會(huì)的,因?yàn)閮蓚€(gè)迭代過程使用的是不同的數(shù)組對(duì)象,迭代的成員完全不沖突因此不會(huì)造成冗余或漏掉的情況,畢竟就是一一匹配的。

下面我們來調(diào)試一下,看看這樣的篩選是否成功:

我們來看一下運(yùn)行結(jié)果吧。

合理。答案是正確的。不過,答案使用的是下弦之鬼的真名,因此對(duì)于不熟悉的人來說,看著不太友好。我們給 Demon 的構(gòu)造器加上一個(gè)參數(shù),表示下弦級(jí)別;然后顯示結(jié)果的時(shí)候也是顯示它的級(jí)別;順帶加上重寫的 ToString 方法,以便顯示和輸出對(duì)應(yīng)的結(jié)果。

注意第 14 行的這個(gè)寫法。我們是直接在數(shù)組后加上的索引器。這種寫法有些時(shí)候是可以的,它表示直接在“即定義即用”的數(shù)組序列里去取值。接著,Level 的范圍是 1 到 6,而數(shù)組下標(biāo)從 0 開始,因此我們給這個(gè)“即定義即用”的數(shù)組的第一個(gè)元素配上了 null 數(shù)值占位,保證 Level 是 1 的時(shí)候,直接取序列的第 2 個(gè)元素(索引的時(shí)候?qū)懗?[1],而這個(gè) 1 就是 Level 的值)。

然后,同步地更改構(gòu)造器傳參:

然后,別忘了改一下 select 從句部分,把原來的 .Name 屬性引用部分給去掉。因?yàn)閷懥?Name 還是在顯示對(duì)象的名字:

接著,我們來看一下結(jié)果:

完美。我們完成了對(duì)應(yīng)關(guān)系的查詢。

不過,這樣的寫法有點(diǎn)復(fù)雜,有沒有稍微簡(jiǎn)單一點(diǎn)的寫法來完成計(jì)算?有。

Part 2 join-in-on-equals 從句

我們現(xiàn)在使用一個(gè)全新的從句類型:join-in-on-equals 從句。我們先考慮把兩個(gè)數(shù)組提取出來單獨(dú)搞成變量,以免查詢表達(dá)式過長(zhǎng):

接著,我們?cè)瓉淼恼Z句是這樣的:

現(xiàn)在,我們將第 3 行的 from pet in pets 替換為 join pet in pets;而 where pet.Owner == demon 替換為 on pet.Owner equals demon,并將修改后的代碼寫到一行上去:

是的。這就是新的從句類型:join-in-on-equals 從句??梢钥吹疥P(guān)鍵字一共是 4 個(gè),每?jī)蓚€(gè)關(guān)鍵字的中間都會(huì)插入一個(gè)表達(dá)式的數(shù)據(jù)進(jìn)去,這樣比起原來寫兩層 from-in 從句的好處就是更具有可讀性。

join-in-on-equals 從句的語法如下:

我相信你通過前文的改法可以直接看出這個(gè)寫法的神奇之處。這個(gè)寫法我們直接用 equals 關(guān)鍵字來表示了相等判斷,而 from-in-where 兩行被改成了一行。而改成這樣的語法后,equals 兩側(cè)的對(duì)象直接作為比較信息出現(xiàn),于是其中一個(gè)用的是 pet 對(duì)象的信息參與比較,而另外一個(gè)則用到的是 join 從句上面的 from 從句提供的迭代變量的信息參與比較。這樣的書寫模式使得“查詢集合和集合之間的關(guān)系”的操作更具有可讀性和體系化的處理模式。

因?yàn)檫@樣的代碼從更普通的 from-from-where-select 改成了更具體系化的 from-join-select,因此我們也給這樣的從句模式取了一個(gè)術(shù)語名詞。我們認(rèn)為,我們?cè)谥鹨黄ヅ淦陂g,篩選掉了不滿足條件的內(nèi)容,通過 equals 兩側(cè)的對(duì)象比較相等性作為基本判斷的操作,因此結(jié)果是來自于兩個(gè)列表的元素構(gòu)成的集合。比如前面的例子里,下弦之鬼有 6 個(gè),而寵物也有 6 個(gè),如果一一匹配的話,得到的總組合情況數(shù)量肯定是 36 個(gè);但因?yàn)?equals 兩側(cè)對(duì)象的比較過程的篩選,導(dǎo)致了最終的合理匹配結(jié)果不足 36 個(gè)(當(dāng)然,如果你寫的是 on true equals true 的話……這個(gè)反人類的情況就不多說了)。我們把這種邏輯上整合兩個(gè)表的數(shù)據(jù)湊成新結(jié)果的操作稱為連接(Join),而我們把邏輯上“取交集”的拼接操作稱為內(nèi)連接(Inner Join)。

至于別的連接操作,我們會(huì)在本文稍微靠后一點(diǎn)的地方介紹,因?yàn)楸容^多也比較復(fù)雜。

Part 3 join-in-on-equals-into 從句

into 關(guān)鍵字是老熟人了,在 selectgroup 里都有所使用。今天我們要看看的是 into 搭配 join 的用法。不過,它比 group-by-into 還要難理解一點(diǎn),所以很多同學(xué)初學(xué) LINQ 的時(shí)候都栽在這里了。

3-1 join 往往體現(xiàn)的是“一對(duì)多”的用法

要想理解 join 后拼接 into 的用法,我們就得回憶一下,join 用起來的意義。join 在前文是用來代替兩層連續(xù)的 from 從句的,目的就是為了讓可讀性提高一些。因?yàn)?fromfrom 的拼接看起來就很“普通”,于是不容易讓用戶思考和想象出來它的真實(shí)用法,而給出 from-join 后就會(huì)立馬知道,join 的出現(xiàn)就是為了匹配前文給出的信息列表的,于是使用范疇就會(huì)稍微變窄一點(diǎn),這樣的方式提升的可讀性。

而整個(gè) join 要用到四個(gè)關(guān)鍵字:joinin、onequals,每?jī)蓚€(gè)相鄰關(guān)鍵字之間都會(huì)插入信息和變量來維持語法的正確性。整個(gè) join 的用法是:

  • join-in 兩個(gè)關(guān)鍵字中間的變量表示迭代和匹配前面的列表的單位對(duì)象;

  • in-on 兩個(gè)關(guān)鍵字中間的變量表示迭代的列表對(duì)象;

  • onequals 兩個(gè)關(guān)鍵字中間的變量表示匹配期間的條件到底是“誰和誰相同”的第一個(gè)變量;

  • equals 后的變量表示匹配期間的條件到底是“誰和誰相同”的第二個(gè)變量。

大概從這樣的介紹文字里看出,它就是 from-in 的翻版寫法。不過,這里 into 是直接跟在 join-in-on-equals 之后的,所以不能拿原來的思路去理解同樣的東西了。那怎么去理解呢?

試想一下,join 的目的是為了什么?目的是為了連接前文 from 的迭代序列,對(duì)吧。我要匹配正常的數(shù)據(jù),要使得某個(gè)條件上兩個(gè)對(duì)象的數(shù)據(jù)是一致的,那么它們就放在一起。那么,像是剛才的下弦鬼和寵物的對(duì)應(yīng)關(guān)系來說,鬼可以有多個(gè)寵物,所以是“一對(duì)多”的關(guān)系——一個(gè)鬼可以有多個(gè)寵物,鬼是“一”而寵物就是“多”。而正是因?yàn)樗w現(xiàn)的是“多”的這部分,所以我們把它放在了 join 這部分的迭代過程里,而沒有反過來寫(join-in 迭代 pets,而 join-in-on-equals 則迭代的是 demons)。

這么做是有意義的。join 體現(xiàn)的確實(shí)是一種一對(duì)多的關(guān)系,而 join 這個(gè)從句的迭代部分是作為“多”的體現(xiàn)的,而它上面緊挨著的這個(gè) from 體現(xiàn)的是“一”。這么說起來就比較好理解了:這個(gè) into 表示的是這個(gè)“一對(duì)多”過程里的“多”里的所有結(jié)果。這么說比較抽象,換到這個(gè)例子里來的話:

我如果這么寫代碼的話,那么這里的 gj 變量,表示的就是當(dāng)前的下弦鬼的所有寵物。可以看到,我們后面接了一個(gè) select 從句,直接使用匿名類型的第二個(gè)屬性 Pets 接收了這個(gè) gj 變量。那么這個(gè) selection 變量整體是啥意思呢?就是在獲取所有下弦鬼的每一個(gè)鬼的寵物信息,然后整合成匿名類型的表達(dá)形式反饋出來。

用的時(shí)候,就這么用就可以了:

首先我們來迭代 selection 變量,每一個(gè)變量都是一個(gè)匿名類型的實(shí)例。接著,我們要取出下弦鬼的名字,然后和它的寵物。這里我們要特別注意一點(diǎn):這里的“一對(duì)多”是概念上的理解方式,在實(shí)際體現(xiàn)里,可能有下弦鬼沒有任何的寵物與之對(duì)應(yīng),這是存在的。正是因?yàn)橛羞@種情況,我們才需要判斷一下。這里我們要用到一個(gè)擴(kuò)展方法:Any。

Any 擴(kuò)展方法是顯式無參的(Explicitly Parameterless),即該方法是滿足擴(kuò)展方法的一切規(guī)則,而它實(shí)際上也只包含一個(gè) this 修飾過的參數(shù),而它因?yàn)閿U(kuò)展方法的語法被我們進(jìn)行實(shí)例前置了,所以在按照擴(kuò)展方法的語法規(guī)則書寫之后,這個(gè)參數(shù)就沒了,于是變?yōu)榱艘粚?duì)空的小括號(hào)。

我們直接跟在 currentPets 變量之后,表示的是這個(gè)集合到底有沒有元素。如果有元素,那么就返回 true;否則返回 false。這一點(diǎn)和普通集合的 CountLength 屬性 != 0 邏輯是一樣的。只不過這里 currentPets 的實(shí)際類型不允許我們這么做——它實(shí)際上是 IEnumerable<Pet> 類型的。是的,join 后用的 into 從句,這個(gè)變量都應(yīng)為 IEnumerable<T> 的類型,而這里的 T 會(huì)根據(jù)你的 join 迭代的類型以及判斷的操作進(jìn)行調(diào)整。

3-2 分組連接的概念

現(xiàn)在我們大概能知道用 intojoin 之后是啥意思了,但是,這個(gè)變量名就很奇怪:gj?

實(shí)際上,在 LINQ 里,join 后跟 into 的語法被官方稱為分組連接(Group Join)。分組連接這個(gè)名字乍一看其實(shí)是不容易理解的,因?yàn)檫B接還帶分組就很離譜。但實(shí)際上,仔細(xì)思考和分析一下前面的例子就可以發(fā)現(xiàn),它確實(shí)是在連接的操作期間使用了“分組”的操作。只不過,這里的分組和上一講的分組語法 group 從概念上完全不同:這里說的“分組”,單純是指代和說明“一對(duì)多”關(guān)系里的“多”有很多,所以將對(duì)象的對(duì)應(yīng)“多”的結(jié)果全部整合到 into 后跟的這個(gè)變量里去。而思考一下實(shí)際上,這個(gè)操作也確實(shí)可以叫分組了,畢竟完整完成查詢操作之后,所有的“一對(duì)多”關(guān)系都體現(xiàn)出來了,而每一個(gè)對(duì)象都有若干對(duì)應(yīng)匹配的成員,這就是分組的思路和思維方式。

分組連接的英文 group join 的首字母是 g 和 j,所以這個(gè)變量名才叫的是 gj。實(shí)際上,在微軟提供的 LINQ 語法教學(xué)里,gj 這個(gè)“約定俗成”的變量名也廣泛存在。比如這樣

還有這樣

兩個(gè)圖片均出自于這個(gè)頁面的代碼:

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/join-clause

雖然不一定都看得懂別人的代碼吧,但是你可以發(fā)現(xiàn),gj 也都“不謀而合”地出現(xiàn)在 join 從句的 into 關(guān)鍵字之后。

Part 4 說說細(xì)節(jié)

join 雖然好用,看懂了基本上就可以上手用了,但是它還是有很多細(xì)節(jié)需要我們說明清楚的。下面我們都來看看。

4-1 equals 關(guān)鍵字比較的細(xì)節(jié)

equals 左右兩側(cè)都會(huì)跟上一個(gè)變量,它表示的是什么樣子相同的條件的兩個(gè)對(duì)象才能匹配關(guān)聯(lián)起來。而這個(gè)是一個(gè)關(guān)鍵字,因此導(dǎo)致用起來仍然不夠靈活,畢竟你不能使用比如運(yùn)算符啊、比如方法調(diào)用之類的操作來判斷比較條件。

不過,這么限制是有意義的。在查詢操作期間,百分之八九十都是在進(jìn)行等值連接(Equivalent Join),即進(jìn)行 join 連接期間,用的是相等性比較,所以設(shè)計(jì)為關(guān)鍵字也是不必深究的點(diǎn)——畢竟大多數(shù)時(shí)候都是等值連接。

話說回來,那么 equals 到底是啥樣的判斷規(guī)則和過程呢?實(shí)際上,很簡(jiǎn)單。就是咱們之前學(xué)習(xí)的 EqualityComparer<> 的判斷模式。equals 會(huì)把代碼翻譯成使用類型默認(rèn)的判斷比較規(guī)則來進(jìn)行比較的過程。就是什么檢查類型的 Equals 方法和 GetHashCode 方法的重寫啊,先調(diào)用誰后調(diào)用誰啊,這些。

所以,你的代碼要想?yún)⑴c equals 關(guān)鍵字的比較操作,至少得保證對(duì)象實(shí)體包含 Equals 方法和 GetHashCode 方法的重寫。

4-2 join 里的 into 不會(huì)像 selectgroup 里那樣阻斷查詢

這一點(diǎn)比較特殊。因?yàn)樵谠O(shè)計(jì)分組連接的語法的時(shí)候,因?yàn)?LINQ 的關(guān)鍵字太多了,所以官方也不打算搞得太復(fù)雜,所以還是用了 into 來表示分組連接的結(jié)果。不過,這里的 into 不會(huì)阻斷查詢,因此你仍然可以使用前面的變量。

再來看之前這個(gè)例子就可以發(fā)現(xiàn),我們?cè)?select 里仍然使用到了 into 以前出現(xiàn)的 demon 變量。這一點(diǎn)希望你記清楚。

Part 5 靈活使用 join 從句

這個(gè)術(shù)語詞不是 LINQ 發(fā)明的,在計(jì)算機(jī)學(xué)科的關(guān)系代數(shù)(Relation Algebra)分支上,按照完成的分類,連接操作有這樣的一些情況:

  • 按連接的機(jī)制分類:

    • 左外連接不含內(nèi)連接(左減右):使用左外連接取所有結(jié)果的同時(shí),去掉都包含的情況;

    • 右外連接不含內(nèi)連接(右減左):使用右外連接取所有結(jié)果的同時(shí),去掉都包含的情況;

    • 全外連接不含內(nèi)連接(對(duì)稱差):使用全外連接取所有情況的同時(shí),去掉都包含的情況。

    • 左外連接(也叫左連接,Left Outer Join):將左邊的列表當(dāng)成基準(zhǔn),右邊列表的元素往左邊列表上拼接(拼接在左邊列表的右側(cè))。如果沒有的部分字段會(huì)保持 null 數(shù)值;

    • 右外連接(也叫右連接,Right Outer Join):將右邊的列表當(dāng)成基準(zhǔn),左邊列表的元素往右邊列表上拼接(拼接在右邊列表的左側(cè))。如果沒有的部分字段會(huì)保持 null 數(shù)值;

    • 全外連接(也叫全連接,F(xiàn)ull Outer Join):不按具體那個(gè)列表當(dāng)基準(zhǔn),而是取出兩個(gè)列表的全部情況,左邊列表沒有的部分補(bǔ)充 null 數(shù)值,而右邊列表沒有的部分補(bǔ)充 null 數(shù)值,并湊成一個(gè)列表。

    • 外連接(Outer Join)

    • 內(nèi)連接(Inner Join):基本連接手段。只有兩個(gè)列表都包含的數(shù)據(jù)才拼接起來,如果只有一個(gè)列表里有這個(gè)數(shù)據(jù),而另外一個(gè)表沒有,這樣的數(shù)據(jù)會(huì)被忽略掉,以至最終結(jié)果里沒有它的出現(xiàn)。

    • 排除連接(Excluding Join):將前面的連接操作混合使用的復(fù)雜連接方式。

  • 按連接的條件分類:

    • 等值連接(Equivalent Join,簡(jiǎn)稱 Equijoin):將連接操作期間使用的條件(比如內(nèi)連接的拼接依據(jù))設(shè)定為相等性判斷的連接操作;

    • 非等值連接(Inequivalent Join,簡(jiǎn)稱 Non-equijoin):將連接操作期間使用的條件(比如內(nèi)連接的拼接依據(jù))設(shè)定為不等性判斷的連接操作;

    • 自然連接(Natural Join):將連接操作期間使用的條件設(shè)定為相等性判斷的連接操作,并在連接之后直接刪除掉重復(fù)的列;

    • 交叉連接(Cross Join):就是笛卡爾積,沒有任何條件,直接兩兩直接拼接即可;

    • 組合鍵連接(Join by Composite Key):使用復(fù)雜的對(duì)象作為條件判斷依據(jù)的連接。

  • LINQ 的特有連接機(jī)制:

    • 分組連接(Group Join):將連接的操作結(jié)果臨時(shí)定義為一個(gè)單獨(dú)的組用于后續(xù)操作。

我知道,是挺復(fù)雜的,所以我并不打算全都說明,畢竟 LINQ 的 join 也確實(shí)不能做到前面提到的這些全都的連接操作,而且內(nèi)連接、分組連接以及等值連接已經(jīng)在前面說過了。

5-1 DefaultIfEmptyFirst 擴(kuò)展方法

為了銜接后面的內(nèi)容,我們需要優(yōu)先介紹一個(gè) .NET 自帶的方法操作:DefaultIfEmpty 擴(kuò)展方法。這個(gè)方法比較繞。我還是舉例說明一下。

這個(gè)調(diào)用操作表示,如果 pets 集合序列沒有包含任何元素(即 Any 方法調(diào)用后返回 false),那么這個(gè)方法調(diào)用將會(huì)返回一個(gè)包含 default(Pet) 的元素的集合;否則,pets 里包含元素,于是就把它自己返回出來。

這個(gè)做法有什么意義呢?DefaultIfEmpty 被設(shè)計(jì)出來主要是為了提供默認(rèn)集合。所謂的提供默認(rèn)集合,主要的地方用在哪里呢?考慮一種情況。假設(shè)我們有這樣的操作,在已經(jīng)篩選的集合里進(jìn)行匹配。

現(xiàn)在我們要獲取這個(gè)序列的第一個(gè)元素,我們可以使用系統(tǒng)提供的 First 擴(kuò)展方法:

可問題是,這個(gè)方法有個(gè)問題在于,如果序列集合沒有元素的話,就會(huì)拋出異常,畢竟沒有元素為啥還要取元素呢?于是,我們需要手動(dòng)判斷:

現(xiàn)在,這個(gè)方法從另外一個(gè)角度允許了我們少使用復(fù)雜的處理過程:

DefaultIfEmpty 方法表示,如果序列沒有元素,那么就會(huì)默認(rèn)返回一個(gè)集合,這個(gè)集合只包含一個(gè)元素,這個(gè)元素的值就是這個(gè)集合每一個(gè)元素應(yīng)該的數(shù)據(jù)類型 T 的默認(rèn)數(shù)值 default(T)。有點(diǎn)繞,簡(jiǎn)單來說就是,序列的每一個(gè)元素是 Demon 類型的,而 DefaultIfEmpty 方法調(diào)用的時(shí)候,如果序列沒有元素,那么就會(huì)默認(rèn)產(chǎn)生一個(gè)集合進(jìn)行返回,該集合只有一個(gè)元素,數(shù)值是 default(Demon),也就是 null。于是,我們可以針對(duì)于這個(gè)集合繼續(xù)進(jìn)行 First 方法的調(diào)用,這樣就避免了異常拋出。而且這個(gè)辦法巧妙的是,我們省去了條件運(yùn)算符(或者 if 語句)的使用;null 直接通過 DefaultIfEmpty 的默認(rèn)情況直接體現(xiàn)出來了,于是不用我們手寫出來。

當(dāng)然了,如果你要自定義默認(rèn)返回值的話(即想改變返回默認(rèn)的情況是 null 還是別的什么的時(shí)候),你也可以給這個(gè) DefaultIfEmpty 方法上額外傳入一個(gè)參數(shù),調(diào)用它的重載版本:

這里我們定義了一個(gè)默認(rèn)對(duì)象,它表示如果集合里沒有元素的時(shí)候會(huì)默認(rèn)返回一個(gè)帶有這個(gè)對(duì)象的集合。它等價(jià)于

明白了嗎?

5-2 左外連接和右外連接

要知道,join 也并非只能放在 select 之前緊挨著的地方。C# 的 LINQ 十分靈活,正是因?yàn)樗撵`活度,所以上面的連接操作才會(huì)有文章可以寫。

前面我們講的是內(nèi)連接,內(nèi)連接不會(huì)產(chǎn)生額外的信息,畢竟是內(nèi)部相同比較匹配。但是,如果要想去做到外連接(Outer Join)的話,就比較麻煩了。

我們先要說的是外連接的基本概念。外連接是讓一個(gè)列表作為基本列表,然后讓另外一個(gè)列表去往上匹配。這個(gè)作為基本列表的表按照一定的條件和另外一個(gè)列表的元素進(jìn)行匹配,如果找到了的話,就連接起來。但是,如果基本列表里有,但往上拼接的表里沒有的元素的話,就會(huì)被補(bǔ)上默認(rèn)數(shù)值。

外連接和內(nèi)連接的區(qū)別就在于這一點(diǎn)。外連接有基本列表,這會(huì)使得拼接和連接的操作更加嚴(yán)謹(jǐn)和細(xì)節(jié);而內(nèi)連接因?yàn)椴恍枰紤]連不上的情況,因此會(huì)更實(shí)用。

舉個(gè)例子吧。假設(shè)我現(xiàn)在有兩個(gè)列表,一個(gè)是下弦之鬼,一個(gè)則是寵物。我們要讓寵物的主人作為拼接條件,和下弦之鬼逐個(gè)進(jìn)行判斷和拼接。不過,有些下弦之鬼是沒有寵物的,因此這個(gè)時(shí)候在前文的例子里,我們并不能體現(xiàn)出它們。如果要體現(xiàn)出它們的話,我們需要使用左外連接來完成這個(gè)任務(wù)。

左外連接就是基于左列表的外連接,即將操作的兩個(gè)列表里的左邊這個(gè)作為基本列表?,F(xiàn)在我們要取出這種對(duì)應(yīng)關(guān)系的時(shí)候,有些下弦之鬼是沒有寵物的,但是我們?nèi)耘f需要體現(xiàn)出來,因此“下弦之鬼”這個(gè)列表就得作為基本列表;而“寵物”則是附加的表。來看看怎么做。

請(qǐng)看示例。這個(gè)例子里,我們改變了一下代碼的執(zhí)行邏輯,雖然我們?nèi)匀皇褂昧朔纸M連接,但這個(gè)時(shí)候我們?cè)?join 后又一次跟了一個(gè) from 從句,目的是為了匹配和顯示我們這里的左外連接的運(yùn)算規(guī)則。

我們?cè)?from subpet in gj.DefaultIfEmpty 這個(gè)部分里用到了 DefaultIfEmpty 方法的調(diào)用,這里就可以看到 DefaultIfEmpty 的作用了:DefaultIfEmpty 即使里面沒有元素,也會(huì)產(chǎn)生一個(gè)默認(rèn)元素,不影響迭代。

然后我們把取出的對(duì)應(yīng)關(guān)系序列,按照 select 從句進(jìn)行映射。注意這里的第二個(gè)屬性是 Pet 了,因?yàn)檫@里我們是迭代了 gj 的序列,因此不再是整個(gè)返回。那么,怎么用這個(gè)變量呢?

這樣就可以了。這就是左外連接。我們將“左列表”(下弦之鬼)作為基本表,永遠(yuǎn)顯示里面的成員;而“右列表”(寵物)作為附加規(guī)則給附加上去。如果說右列表里沒有對(duì)應(yīng)的情況的話,我們將會(huì)輸出默認(rèn)數(shù)值(對(duì)應(yīng)了這里的第 6 行輸出代碼)。這就是左外連接。

那么右外連接呢?很遺憾,C# 的 LINQ 不能實(shí)現(xiàn)右外連接。本節(jié)結(jié)束。

雖然我很想說這是開玩笑這是調(diào)侃,但確實(shí)如此。C# 的 LINQ 并不能做到和模擬右外連接。你只能將表格反過來使,交換了左右列表后,再使用左外連接的方式來模擬出右外連接的運(yùn)算。實(shí)際上,通過上面的例子也看得出來,這種連接操作也就左外連接比較好用,也比較實(shí)用。

5-3 非等值連接

由于 join 里用 equals 關(guān)鍵字是固定行為,所以你無法使用 join 來達(dá)成除了相等匹配以外的所有情況。不過,這也不代表我們做不到。由于 joinfrom 改過來的,所以我們?nèi)匀豢梢允褂玫芽柗e來完成這一點(diǎn)。

假如我有一系列的商品,每一個(gè)商品都貼有標(biāo)簽標(biāo)識(shí)它是啥類型的商品。我現(xiàn)在要想找到合適的商品,這些商品的標(biāo)簽必須在我現(xiàn)在給定的標(biāo)簽序列里包含有的情況。這怎么找呢?

這要是寫成 join 的話,由于條件沒辦法使用 Contains 這樣的情況,因此無法做到。

5-4 表達(dá)式為 join 連接條件的連接

假設(shè)我們?nèi)匀皇褂眠@個(gè)例子來舉例,只不過這次我們不直接匹配名稱了,而是去模擬匹配。假設(shè)像是張三李四王五趙六這樣,我們只要求姓氏相同就可以連起來的話,join 就會(huì)這樣做:

這里的 Split 方法是將字符串按指定字符進(jìn)行切割。遇到這個(gè)字符的時(shí)候,就拆開字符串的左右兩側(cè),以這個(gè)字符為分界點(diǎn)。那么 demon.Split(' ')pet.Split(' ') 的作用就是將單詞按空格分割,于是得到的結(jié)果就自然是姓名了。我們?nèi)〕龅谝粋€(gè)部分,自然就是這個(gè)人的姓。

特別注意,由于 equals 兩側(cè)的數(shù)據(jù)是 string 類型,而 string 實(shí)現(xiàn)了比較相等的規(guī)則,因此這樣的寫法是允許的。

5-5 組合鍵連接

LINQ 還能進(jìn)行組合鍵連接。上面我們講了一下如何使用表達(dá)式進(jìn)行相等性比較,甚至是……組合鍵(Composite Key)。

所謂的組合鍵,在 LINQ 里你可以理解成 equals 兩側(cè)的對(duì)象為匿名類型的情況。

好比我現(xiàn)在有這么一個(gè)序列。這個(gè)序列里包含眾多的用戶點(diǎn)單信息(Orders 屬性)。現(xiàn)在,我要獲取的是商品和用戶點(diǎn)單之間的這么一個(gè)關(guān)系的話,我們就需要使用組合鍵連接。

這里,我們將 join 從句的 equals 判斷部分寫成匿名類型的相等性比較,標(biāo)識(shí)我除了商品要一致以外,點(diǎn)單的單號(hào)也要一致,于是就有了上面展示的專業(yè)的代碼的判斷過程。

至此,我們就將 join 的相關(guān)用法都給大家全部介紹了一遍。LINQ 的關(guān)鍵字就算是講完了。下一講我們不打算講解內(nèi)容,先給大家布置一些有關(guān) LINQ 語法可以做到的查詢操作作為練習(xí),給大家練習(xí)練習(xí)。


第 102 講:C# 3 之查詢表達(dá)式(六):join、on 和 equals 關(guān)鍵字的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
大悟县| 神池县| 安泽县| 贵州省| 梁山县| 乐亭县| 开封市| 通州市| 东丰县| 锦屏县| 民勤县| 临沧市| 洛隆县| 巨野县| 集安市| 佛坪县| 射阳县| 布尔津县| 高碑店市| 万年县| 淳安县| 德安县| 金昌市| 甘孜县| 托克托县| 遂川县| 钦州市| 六枝特区| 南城县| 会同县| 循化| 昌平区| 长丰县| 靖州| 金沙县| 藁城市| 崇义县| 稻城县| 玉龙| 汝阳县| 肥城市|