C#複習筆記(4)--C#3:革新寫代碼的方式(查詢運算式和LINQ to object(下))

來源:互聯網
上載者:User

標籤: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(下))

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.