LINQ 的查詢執(zhí)行何時是延遲執(zhí)行,何時是立即執(zhí)行,以及查詢的復(fù)用 延遲執(zhí)行的經(jīng)典例子: 我們用 select ++i 就可以看到在foreach 時候,查詢才被執(zhí)行。 public static void Linq99() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = from n in numbers select ++i; foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); } 輸出結(jié)果: v = 1, i = 1 v = 2, i = 2 v = 3, i = 3 v = 4, i = 4 v = 5, i = 5 v = 6, i = 6 v = 7, i = 7 v = 8, i = 8 v = 9, i = 9 v = 10, i = 10 foreach每一個遍歷的時候,select出來的值和當(dāng)前i的值都是一樣的。 立即執(zhí)行的經(jīng)典例子: public static void Linq99() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = (from n in numbers select ++i).ToList(); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); } 執(zhí)行結(jié)果: v = 1, i = 10 v = 2, i = 10 v = 3, i = 10 v = 4, i = 10 v = 5, i = 10 v = 6, i = 10 v = 7, i = 10 v = 8, i = 10 v = 9, i = 10 v = 10, i = 10 這個例子的代碼跟上面延遲執(zhí)行的例子代碼唯一的差別在于多了一個.ToList(); 這也可以證明我們之前提到的原則: 只有到用的時候才會去執(zhí)行查詢 由于 .ToList(); 的存在,在這里就要用到了,所以在這里就執(zhí)行了查詢,而不是在foreach中執(zhí)行查詢。注意,這時候出來的結(jié)果是一個數(shù)組了.參看后面的幾個例子. 執(zhí)行的一個特殊情況:重復(fù)執(zhí)行 請看下面例子: 查詢出一個int數(shù)組中小于3的數(shù)字。 下面例子中在第一次查詢后,對數(shù)據(jù)源作了修改,然后再作第二次查詢,我們可以看到第二次我們不需要再作 lowNumbers = from n in numbers where n <= 3 select n; 這樣的定義,而是直接使用 foreach (int n in lowNumbers)。另外這兩次的返回結(jié)果是不同的,因為我們 在第一次查詢后,對數(shù)據(jù)源作了修改。 public static void Linq101() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var lowNumbers = from n in numbers where n <= 3 select n; Console.WriteLine("First run numbers <= 3:"); foreach (int n in lowNumbers) Console.WriteLine(n); for (int i = 0; i < 10; i++) numbers[i] = -numbers[i]; Console.WriteLine("Second run numbers <= 3:"); foreach (int n in lowNumbers) Console.WriteLine(n); } 輸出結(jié)果: First run numbers <= 3: 1 3 2 0 Second run numbers <= 3: -5 -4 -1 -3 -9 -8 -6 -7 -2 0 下面我們再來看幾個例子,加深對查詢執(zhí)行的理解: 重復(fù)查詢的再一個例子: public static void Linq102() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = from n in numbers select ++i; foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); } 執(zhí)行結(jié)果: v = 1, i = 1 v = 2, i = 2 v = 3, i = 3 v = 4, i = 4 v = 5, i = 5 v = 6, i = 6 v = 7, i = 7 v = 8, i = 8 v = 9, i = 9 v = 10, i = 10 v = 11, i = 11 v = 12, i = 12 v = 13, i = 13 v = 14, i = 14 v = 15, i = 15 v = 16, i = 16 v = 17, i = 17 v = 18, i = 18 v = 19, i = 19 v = 20, i = 20 只執(zhí)行一次的立即查詢: public static void Linq102() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = (from n in numbers select ++i).ToList(); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); } 執(zhí)行結(jié)果: v = 1, i = 10 v = 2, i = 10 v = 3, i = 10 v = 4, i = 10 v = 5, i = 10 v = 6, i = 10 v = 7, i = 10 v = 8, i = 10 v = 9, i = 10 v = 10, i = 10 v = 1, i = 10 v = 2, i = 10 v = 3, i = 10 v = 4, i = 10 v = 5, i = 10 v = 6, i = 10 v = 7, i = 10 v = 8, i = 10 v = 9, i = 10 v = 10, i = 10 那些函數(shù)會導(dǎo)致立即執(zhí)行查詢: 以下幾個擴展函數(shù)會導(dǎo)致LINQ會立即執(zhí)行。并且只執(zhí)行一次。 .ToArray(); .ToList(); .ToDictionary(k => k); 比如: public static void Linq102() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = (from n in numbers select ++i).ToDictionary(k => k); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); } 輸出結(jié)果就是: v = [1, 1], i = 10 v = [2, 2], i = 10 v = [3, 3], i = 10 v = [4, 4], i = 10 v = [5, 5], i = 10 v = [6, 6], i = 10 v = [7, 7], i = 10 v = [8, 8], i = 10 v = [9, 9], i = 10 v = [10, 10], i = 10 v = [1, 1], i = 10 v = [2, 2], i = 10 v = [3, 3], i = 10 v = [4, 4], i = 10 v = [5, 5], i = 10 v = [6, 6], i = 10 v = [7, 7], i = 10 v = [8, 8], i = 10 v = [9, 9], i = 10 v = [10, 10], i = 10 小結(jié): Q:通過上面幾個例子,我們該如何理解LINQ的查詢何時執(zhí)行呢? A:LINQ的查詢執(zhí)行遵循以下原則: 1、一般情況下(除了下面第三條說的情況),LINQ都是延遲執(zhí)行,原因:以DLINQ為例,越晚被執(zhí)行,對業(yè)務(wù)邏輯的理解就越清晰,DLINQ查詢對數(shù)據(jù)庫的請求壓力越小。編譯器對LINQ查詢優(yōu)化可作的事情越多。 2、由于是延遲執(zhí)行,也就是調(diào)用的時候才去執(zhí)行。這樣調(diào)用一次就被執(zhí)行一次,這樣就具備了重復(fù)執(zhí)行的功能,參看之前的幾個重復(fù)執(zhí)行的例子。而這個重復(fù)執(zhí)行是不需要再此書寫一邊查詢語句的。 3、如果查詢中我們對查詢結(jié)果使用了 ToArray、ToList、ToDictionary 這些轉(zhuǎn)換成集合的擴展方法。使用這時候出來的對象是一個獨立的集合數(shù)組,而不是LINQ查詢,所以這時候不會出現(xiàn)多次查詢,而只是一次查詢。 即:var q = from n in numbers select ++i ; 這樣一條語句我們可以認為它記錄的不是等號右邊的結(jié)果,而是記錄的等號右邊的表達式。 而 var q = (from n in numbers select ++i).ToDictionary(k => k); 這樣一條語句我們記錄的是等號右邊的計算結(jié)果,而不是表達式。 為理解上面說明,我們可以再看兩個例子: public static void Linq102() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = from n in numbers select ++i; var qq = q.ToDictionary(k => k); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); foreach (var v in q) Console.WriteLine("v = {0}, i = {1}", v, i); } 輸出結(jié)果: v = 11, i = 11 v = 12, i = 12 v = 13, i = 13 v = 14, i = 14 v = 15, i = 15 v = 16, i = 16 v = 17, i = 17 v = 18, i = 18 v = 19, i = 19 v = 20, i = 20 v = 21, i = 21 v = 22, i = 22 v = 23, i = 23 v = 24, i = 24 v = 25, i = 25 v = 26, i = 26 v = 27, i = 27 v = 28, i = 28 v = 29, i = 29 v = 30, i = 30 而 public static void Linq102() { int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int i = 0; var q = from n in numbers select ++i; var qq = q.ToDictionary(k => k); foreach (var v in qq) Console.WriteLine("v = {0}, i = {1}", v, i); foreach (var v in qq) Console.WriteLine("v = {0}, i = {1}", v, i); } 輸出結(jié)果為: v = [1, 1], i = 10 v = [2, 2], i = 10 v = [3, 3], i = 10 v = [4, 4], i = 10 v = [5, 5], i = 10 v = [6, 6], i = 10 v = [7, 7], i = 10 v = [8, 8], i = 10 v = [9, 9], i = 10 v = [10, 10], i = 10 v = [1, 1], i = 10 v = [2, 2], i = 10 v = [3, 3], i = 10 v = [4, 4], i = 10 v = [5, 5], i = 10 v = [6, 6], i = 10 v = [7, 7], i = 10 v = [8, 8], i = 10 v = [9, 9], i = 10 v = [10, 10], i = 10 參考資料: 101 LINQ Samples LINQ的經(jīng)典例子-Where,Select、SelectMany、SkipWhile子句中使用數(shù)組索引 Where 子句的用法 我們除了可以如下方式書寫帶Where子句的LINQ外: from p in products where p.UnitsInStock > 0 && p.UnitPrice > 3.00M select p; 還可以對數(shù)組(所有實現(xiàn)了IEnumerable接口的對象都可以)的實體使用 Where 擴展方法。 把一個查詢語句寫成多個擴展函數(shù)的方式,這其實是編譯器處理查詢語句的方法,比如下面的查詢語句: int[] arr = new int[] { 8, 5, 89, 3, 56, 4, 1, 58 }; var m = from n in arr where n < 5 orderby n select n; 編譯器在編譯后,替我們產(chǎn)生的代碼等價于如下的代碼: IOrderedSequence<int> m = arr.Where<int>(delegate (int n) { return (n < 5); }).OrderBy<int, int>(delegate (int n) { return n; }); 下面我們來看一個使用Where擴展方法的例子: 我們有一個字符串?dāng)?shù)組,一次是0到9的英文單詞,我們查詢出這10個字符的長度比它所在數(shù)組的位置 這兩個數(shù)字比較小的英文單詞. 這個查詢可能有些繞口,你可以先看下面這些代碼: public static void LinqDemo01() { string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var shortDigits = digits.Where((dd, aa) => dd.Length < aa); Console.WriteLine("Short digits:"); foreach (var d in shortDigits) Console.WriteLine("The word {0} is shorter than its value.", d); } 輸出結(jié)果: Short digits: The word five is shorter than its value. The word six is shorter than its value. The word seven is shorter than its value. The word eight is shorter than its value. The word nine is shorter than its value. 下面我們就來分析上述代碼中最核心的代碼: digits.Where((dd, aa) => dd.Length < aa); 這行代碼都趕了些什么? 1、Where子句其實是用擴展方法來實現(xiàn)的 微軟替我們實現(xiàn)的 Where 子句對應(yīng)的擴展函數(shù)實際是如下的定義: namespace System.Linq { public delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1); public static class Enumerable { public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate); public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate); } } 其中紅色字體的那個擴展函數(shù),就是我們上面代碼實際使用的擴展函數(shù)。 我們這個擴展函數(shù)參數(shù):Func<TSource, int, bool> predicate 的定義看上面代碼的綠色delegate 代碼。 2、Where 子句參數(shù)書寫的是Lambda 表達式 (dd, aa) => dd.Length < aa 就相當(dāng)于 C# 2.0 的匿名函數(shù)。 LINQ中所有關(guān)鍵字比如 Select,SelectMany, Count, All 等等其實都是用擴展方法來實現(xiàn)的。上面的用法同樣也適用于這些關(guān)鍵字子句。 3、這個Where子句中Lambda 表達式第二個參數(shù)是數(shù)組索引,我們可以在Lambda 表達式內(nèi)部使用數(shù)組索引。來做一些復(fù)雜的判斷。 具有數(shù)組索引的LINQ關(guān)鍵字除了Where還以下幾個Select,SelectMany, Count, All 我們下面就來依次舉例 Select 子句使用數(shù)組索引的例子 下面代碼有一個整數(shù)數(shù)組,我們找出這個數(shù)字是否跟他在這個數(shù)組的位置一樣 public static void LinqDemo01() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var numsInPlace = numbers.Select((num, index) => new { Num = num, InPlace = (num == index) }); Console.WriteLine("Number: In-place?"); foreach (var n in numsInPlace) Console.WriteLine("{0}: {1}", n.Num, n.InPlace); } 輸出結(jié)果: Number: In-place? 5: False 4: False 1: False 3: True 9: False 8: False 6: True 7: True 2: False 0: False 其中我們用到的這個Select子句對應(yīng)的擴展函數(shù)定義,以及其中Func<TSource, int, TResult>委托定義如下: public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector); public delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1); SelectMany 子句使用數(shù)組索引的例子 幾個句子組成的數(shù)組,我們希望把這幾個句子拆分成單詞,并顯示每個單詞在那個句子中。查詢語句如下: public static void Demo01() { string[] text = { "Albert was here", "Burke slept late", "Connor is happy" }; var tt = text.SelectMany((s, index) => from ss in s.Split(' ') select new { Word = ss, Index = index }); foreach (var n in tt) Console.WriteLine("{0}:{1}", n.Word,n.Index); } 結(jié)果: Albert:0 was:0 here:0 Burke:1 slept:1 late:1 Connor:2 is:2 happy:2 SkipWhile 子句使用數(shù)組索引的例子 SkipWhile 意思是一直跳過數(shù)據(jù),一直到滿足表達式的項時,才開始返回數(shù)據(jù),而不管之后的項是否仍然滿足表達式,需要注意他跟Where是不一樣的,Where是滿足條件的記錄才返回,SkipWhile 是找到一個滿足條件的,然后后面的數(shù)據(jù)全部返回。 下面例子返回一個整數(shù)數(shù)組中,這個整數(shù)比他自身在這個數(shù)組的位置大于等于的第一個位置以及之后的數(shù)據(jù)。 public static void Linq27() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var laterNumbers = numbers.SkipWhile((n, index) => n >= index); Console.WriteLine("All elements starting from first element less than its position:"); foreach (var n in laterNumbers) Console.WriteLine(n); } 輸出結(jié)果: All elements starting from first element less than its position: 1 3 9 8 6 7 2 0 First 、FirstOrDefault、Any、All、Count 子句 注意: 101 LINQ Samples 中 First - Indexed、FirstOrDefault - Indexed、 Any - Indexed、All - Indexed、Count - Indexed 這五個例子在 Orcas Beta1中已經(jīng)不在可用,即下面代碼是錯誤的。 public void Linq60() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int evenNum = numbers.First((num, index) => (num % 2 == 0) && (index % 2 == 0)); Console.WriteLine("{0} is an even number at an even position within the list.", evenNum); } public void Linq63() { double?[] doubles = { 1.7, 2.3, 4.1, 1.9, 2.9 }; double? num = doubles.FirstOrDefault((n, index) => (n >= index - 0.5 && n <= index + 0.5)); if (num != null) Console.WriteLine("The value {1} is within 0.5 of its index position.", num); else Console.WriteLine("There is no number within 0.5 of its index position.", num); } public void Linq68() { int[] numbers = { -9, -4, -8, -3, -5, -2, -1, -6, -7 }; bool negativeMatch = numbers.Any((n, index) => n == -index); Console.WriteLine("There is a number that is the negative of its index: {0}", negativeMatch); } public void Linq71() { int[] lowNumbers = { 1, 11, 3, 19, 41, 65, 19 }; int[] highNumbers = { 7, 19, 42, 22, 45, 79, 24 }; bool allLower = lowNumbers.All((num, index) => num < highNumbers[index]); Console.WriteLine("Each number in the first list is lower than its counterpart in the second list: {0}", allLower); } public void Linq75() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddEvenMatches = numbers.Count((n, index) => n % 2 == index % 2); Console.WriteLine("There are {0} numbers in the list whose odd/even status " + "matches that of their position.", oddEvenMatches); } 要實現(xiàn)這個功能,可以用Where 子句,如下: public static void Linq60() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int evenNum = numbers.Where((num,index) =>( num % 2 == 0 && index %2 == 0) ).First(); Console.WriteLine("{0} is an even number at an even position within the list.", evenNum); } public static void Linq63() { double?[] doubles = { 1.7, 2.3, 4.1, 1.9, 2.9 }; double? num = doubles.Where((n, index) => (n >= index - 0.5 && n <= index + 0.5)).FirstOrDefault(); if (num != null) Console.WriteLine("The value {1} is within 0.5 of its index position.", num); else Console.WriteLine("There is no number within 0.5 of its index position.", num); } public static void Linq68() { int[] numbers = { -9, -4, -8, -3, -5, -2, -1, -6, -7 }; bool negativeMatch = numbers.Where((n, index) => n == -index).Any(); Console.WriteLine("There is a number that is the negative of its index: {0}", negativeMatch); } public static void Linq71() { int[] lowNumbers = { 1, 11, 3, 19, 41, 65, 19 }; int[] highNumbers = { 7, 19, 42, 22, 45, 79, 24 }; bool allLower = lowNumbers.Where((num, index) => num < highNumbers[index]).All(n => true); Console.WriteLine("Each number in the first list is lower than its counterpart in the second list: {0}", allLower); } public static void Linq75() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddEvenMatches = numbers.Where((n, index) => n % 2 == index % 2).Count(); Console.WriteLine("There are {0} numbers in the list whose odd/even status " + "matches that of their position.", oddEvenMatches); }
|