第 107 講:C# 3 之查詢表達(dá)式(十一):let 的底層原理
今天我們來學(xué)習(xí) let
從句的底層。
實際上,它和 select
的代碼基本上是一樣的,因此內(nèi)容并沒有想象的那么復(fù)雜。
Part 1 定義變量不就是多映射一個對象嗎?
首先我們應(yīng)當(dāng)思考一下,允許我們臨時在查詢表達(dá)式的中間定義變量,如果是你應(yīng)該怎么去實現(xiàn)。顯然我們最容易想到的辦法就是拿 Select
映射的時候帶上它就行了,對吧?
假設(shè)我現(xiàn)在有這樣的代碼。我們使用了一次 let
從句,然后把平均數(shù)的結(jié)果求出之后,然后使用在 select
從句里當(dāng)作反饋對象。如果是你,你肯定會說,let
用來定義變量,那么這個變量一定可以在稍后的代碼里使用。那么既然如此,我們肯定會使用一次 Select
方法來完成這個定義。
實際上,果然如此嗎?答案是,是的。它等價于這樣的方法調(diào)用:
我們連續(xù)使用兩次 Select
擴(kuò)展方法。第一次,我們使用 Select
是為了將平均數(shù)值算出來。可問題是這個數(shù)值還會被留著我們稍后使用,因此我們同時需要傳入兩個數(shù)據(jù),一個是原本的 score
匿名類型對象,另外一個則是此時的平均數(shù)值。
這么映射是有必要的。因為我們?nèi)绻话凑?let
從句那樣得到平均值映射出去的話,那么稍后我們就無法繼續(xù)再使用 score
這個匿名類型的變量了,因為第一個 Select
擴(kuò)展方法此時已經(jīng)只映射出了平均數(shù),而不再包含了原始信息,因此我們需要保證原來的數(shù)據(jù)不丟失的同時再次補(bǔ)充新數(shù)值(這個平均數(shù)值),這就是 let
從句的 Select
語句寫法。
接著,我們再次使用一次 Select
擴(kuò)展方法。這次我們因為從上面?zhèn)飨聛淼慕Y(jié)果是一個新的匿名類型:包含 RealScore
屬性和一個 Average
屬性,因此我們此時可以利用這兩個內(nèi)部屬性來表示最終的映射關(guān)系。顯然,我們要的就是這兩個內(nèi)部屬性的數(shù)值整合起來作為反饋結(jié)果。
似乎是合理的。我們試著將鼠標(biāo)放在 let
和 select
兩個關(guān)鍵字上,就可以查看到調(diào)用方法。首先是 let
從句。它轉(zhuǎn)為了一個 Select
擴(kuò)展方法的調(diào)用:

select

這就是 let
從句的原理了。老規(guī)矩,呈現(xiàn)一下關(guān)系對應(yīng)圖。

Part 2 let
從句導(dǎo)致的無法避免的副作用:無法識別冗余性
當(dāng)然了,這里第二個 Select
擴(kuò)展方法的映射好像似乎沒有啥用,畢竟你完全可以直接將前面一個 Select
擴(kuò)展方法得到的結(jié)果直接作為結(jié)果反饋出來,畢竟前面已經(jīng)得到了這樣的匿名類型對象,恰好就是我們現(xiàn)在所使用的。換言之,上面的查詢表達(dá)式本身應(yīng)該翻譯為這樣:
我這里稍微換一下行書寫 Lambda 表達(dá)式,因為太長了,都寫到后面去了。雖然這么寫估計你看著也不太習(xí)慣,但是將就看,起碼這樣比剛才排版要看著舒服點。
但由于第 3 行的 Select
擴(kuò)展方法調(diào)用沒有任何意義,因此可以直接不要:
不過,這是照著前面的查詢表達(dá)式得到的結(jié)果,因此會有如此的“副作用”,這也是不可避免的,畢竟也沒人知道我必須得這么用。這也是為什么我們永遠(yuǎn)建議你,在有必要優(yōu)化的地方盡量使用 select-into
而不是 let
從句的真正原因:因為 let
從句會在編譯器翻譯為方法調(diào)用的時候,多傳入一個對象進(jìn)去,這樣有可能在之后的調(diào)用和處理過程期間導(dǎo)致一定的副作用,比如由于無法識別冗余映射關(guān)系而導(dǎo)致的多一次的方法調(diào)用過程之類的。
Part 3 題外話:說一下 Visual Studio 對匿名類型的顯示
3-1 “臨時類型記號”
不知道你剛才注意到?jīng)]有,Visual Studio 一直是把匿名類型顯示成帶撇號和小寫字母的寫法的。這種記號叫“臨時類型記號”(好吧這個名字是我取的),表示臨時變量定義過程之中,無法表示或表示起來超長的替代記號。

C# 7 的值元組語法顯示成紫色(我這個電腦把它設(shè)置成的紫色,可能在你電腦上它是別的顏色,比如黃綠色):

而 C# 10 的匿名委托類型語法則顯示為深一點的青色:

當(dāng)然,新的特性還需要等著我們?nèi)ヂ剿?,現(xiàn)在才到 C# 3 呢。
3-2 奇奇怪怪的問題:如果記號里的字母用完了呢?
顯然我們這里顯示這些匿名類型的時候,都是用的所謂的 'a
、'b
、'c
之類的記號在顯示,那么字母用完了呢?
這你不用擔(dān)心,如果 26 個小寫字母用完了,它不會再替換為大寫字母,因為這樣容易分不清楚,而是替換為顯示成數(shù)字,比如 '27
、'28
等等。
極端一些,咱們來試一下。

'a