標籤:ati static 針對 -- object from 匿名 led 簽名
查詢運算式和LINQ to object(下)
接下來我們要研究的大部分都會涉及到透明標識符
let子句和透明標識符
let子句不過是引入了一個新的範圍變數。他的值是基於其他範圍變數的。let 標識符=運算式;
首先展示一個不適用let操作符來使用的按使用者名稱稱長度來排序:
... var queryWithoutLet = from user in SampleData.AllUsers orderby user.Name.Length select user; foreach (User user in queryWithoutLet) { Console.WriteLine($"{user.Name}‘s length is {user.Name.Length}"); }...
可以看得出為了按名稱排序被迫使用了兩次Name.Lengthl來進行查詢。這是相當耗費效能的。所以,需要有一種手段來避免這種冗餘的計算方式,這就引出了let操作:它對一個運算式進行求值, 並引入一個新的範圍變數。
... var query = from user in SampleData.AllUsers let length = user.Name.Length orderby length select new { length = length, Name = user.Name }; foreach (var item in query) { Console.WriteLine($"{item.Name}‘s length is {item.length}"); }...
上述代碼產生的結果都相同,只不過只計算了一次Length操作。代碼清單引入了一個新的範圍變數:length,它包含了使用者名稱的長度(針對原始序列中的目前使用者)。我們接著把新的範圍變數用於排序和最後的投影。 你發現問題了嗎? 我們需要使用兩個範圍變數, 但 Lambda運算式只會給Select傳遞一個參數(具體原因在後面)! 這就該透明標識符出場了。
我們在最後的投影中使用了兩個範圍變數, 不過Select方法只對單個序列起作用。 如何把範圍變數合并在一起呢? 答案是,建立一個匿名型別來包含兩個變數,不過需要進行一個巧妙的轉換, 以便看起來就像在select和orderby子句中實際應用了兩個參數。
展示了這個過程:
上述代碼清單的執行過程,其中let子句引入了length範圍變數。
下面是轉譯後的代碼:
...var translatedQuery = SampleData.Users .Select(user => new {user, length = user.Name.Length}) .OrderBy(z => z.length) .Select(z => new {Name = z.user.Name, Length = z.length});...
查詢的每個部分都進行了適當的調整:對於原始的查詢運算式直接引用user或length的地方,如果引用發生在let子句之後, 就用z.user或 z.length來代替。這裡z這個名稱是隨機播放的——一切都被編譯器隱藏起來。
需要進行說明的是,匿名型別只是一種實現的方式,因為在C#規範上面沒有嚴格規定透明標識符的轉移過程,C#規範只是描述了透明標識符應該以怎樣的形式去表現。C#的現有編譯器是通過匿名型別來實現的,以後不知道會怎樣。
連接
LINQ中的連接與Sql上面的連接的概念相似,只不過LINQ上面的連接操作的序列。LINQ有三種各類型的連接,但並不是都是用join關鍵字,首先來看與sql中的內連接相似的join連接。
關於連接,我準備先說一個最重要的結論:連接的左邊會進行串流,而右邊會進行緩衝傳輸,所以,在連接兩個序列時,應該儘可能的將較小的那個序列放到連接的右側。這個結論很重要,所以我準備在章節中多次提及。
MSDN文檔在描述計算內連接的jon方法時,將相關的序列稱作inner和outer(可以查看IEnumerable<T>.Join()方法。)。這個只是用來區分兩個序列的叫法而已,不是真的在指內連接和外連接。對於IEnumerable<T>.Join()來說,outer是指Join的左邊,inner是指Join的右邊。
首先看一下join的文法:
left-key-selector的類型必須要與right-key-selector的類型匹配(能夠進行合理的轉換也是有效),意義上面來說也要相等,我們不能吧一個人的出生日期和一個城市的人口做關聯。
連接的符號是”equals“而不是“=”或者“==”。
我們也完全有可能用匿名型別來作為鍵, 因為匿名型別實現了適當的相等性和散列。 如果想建立一個多列的鍵, 就可以使用匿名型別。
執行個體:
static void Main(string[] args) { var query = from defect in SampleData.AllDefects join subscription in SampleData.AllSubscriptions on defect.Project equals subscription.Project select new { defect.Summary, subscription.EmailAddress }; foreach (var item in query) { Console.WriteLine($"{item.EmailAddress}-{item.Summary}"); } Console.ReadKey(); }
我們可以將join兩邊的序列進行反轉,結果返回的內容相同,只是順序不同,在linq to object的實現中,返回條目的順序為:先使左邊序列中第1個元素的所有成對資料能被返回(按右邊序列的順序),接著返回使用左邊序列中第2個元素的所有成對資料,依次類推。右邊 序列被緩衝處理,不過左邊序列仍然進行流處理—— 所以,如果你打算把一個巨大的序列聯結到一個極小的序列上, 應儘可能把小序列作為右邊序列。這種操作仍然是延遲的:在訪問第1個資料對時,它才會開始執行,然後再從某個序列中讀取資料。這時,它會讀取整個右邊序列,來建立一個從鍵到產生這些鍵的值的映射。之後,它就不需要再次讀取右邊的序列了, 這時你可以迭代左邊的序列,產生適當的資料對。
展示了上述代碼中用作資料來源的兩個不同序列。(SampleData.AllDefects,缺陷和SampleData.AllSubscrption,訂閱)
)
我們通常需要對序列進行過濾,而在聯結前進行過濾比在聯結後過濾效率要高得多。
static void Main(string[] args) { var query = from defect in SampleData.AllDefects where defect.Status==Status.Closed join subscription in SampleData.AllSubscriptions on defect.Project equals subscription.Project select new { defect.Summary, subscription.EmailAddress }; foreach (var item in query) { Console.WriteLine($"{item.EmailAddress}-{item.Summary}"); } Console.ReadKey(); }
我們也能在join右邊的序列上執行類似的查詢,不過稍微麻煩一些:
static void Main(string[] args) { var query = from subscription in SampleData.AllSubscriptions join defect in (from defect in SampleData.AllDefects where defect.Status == Status.Closed select defect) on subscription.Project equals defect.Project select new {subscription.EmailAddress, defect.Summary}; foreach (var item in query) { Console.WriteLine($"{item.EmailAddress}-{item.Summary}"); } Console.ReadKey(); }
說明 內聯結在LINQ to Objects中很有用嗎? SQL總是會使用內聯結。它們實際上是從某個實體導航到相關聯的實體上的一種方式, 通常是把某個表的外鍵和另外一個表的主鍵進行聯結。在物件導向模型中,我們傾向於通過引用來從某個對象導航到另外一個對象。 例如, 在SQL中, 要得到缺陷的概要和處理這個缺陷的使用者名稱稱(這裡的名詞都是針對書面代碼的對象的屬性),需要進行聯結—— 在C#中,我們則使用屬性鏈。如果在我們的模型中存在一個反向關聯,從Project對象到與之關聯的NotificationSubscription對象列表,我們 不必使用聯結也可以實現這個例子的目標。 這並不是說,在物件導向模型裡面,內聯沒有用——只是沒有在關聯式模式中出現得那麼頻繁而已。
內聯被編譯器轉譯後的結果如下:
用於LINQ to object的多載簽章如下:
由於剛才已經對inner和outer的含義做了說明,此處就略去了。
當聯結的後面不是select子句時,C#3編譯器就會引入透明標識符,這樣,用於兩個序列的範圍變數就能用於後面的子句,並且建立了一個匿名型別,簡化了對resultSelector參數使用的映射。然而,如果查詢運算式的下一 部分是select子句,那麼select子句的投影就直接 作為resultSelector參數—— 當你可以一步完成這些轉換的時候,建立元素對,然後調用Select是沒有意義的。你仍然可以把它看做是“select” 步驟所跟隨的“join” 步驟, 儘管兩者都被壓縮到了一個單獨的方法調用中。在我看來,這樣在思維模式上更能保持一致,而且這種 思維模式也容易理解。除非你打算研究產生的程式碼,不然可以忽略編譯器為你完成的這些最佳化。令人高興的是,在學懂了內聯結的相關知識後,下一種聯結類型就很容易理解了。
C#複習筆記(4)--C#3:革新寫代碼的方式(查詢運算式和LINQ to object(下))