一、Linq簡介
Linq的查詢三部曲:一、擷取資料來源;二、建立查詢;三、執行查詢。
二、延遲查詢帶來的好處
延遲查詢有什麼好處呢。請看下面一段代碼:
private static void Main() { var list = new List<int>(); var rd = new Random(); for (var i = 0; i < 100000000; i++) { var num = rd.Next(-10000000, 10000000); list.Add(num); } RecordingTime(list); }
private static void RecordingTime(IEnumerable<int> list) { var watch = new Stopwatch(); watch.Start(); //開始計時 var nums1 = list.Where(n => n > 0); var nums2 = new List<int>(); foreach (var i in list) { if (i > 0) { nums2.Add(i); } } watch.Stop(); //停止計時 var time = watch.ElapsedMilliseconds; Console.WriteLine($"耗時:{time}毫秒"); }
大家可以對比一下num1和num2的運行效率,一億條資料中,使用Linq求集合中的正數耗時僅為2毫秒(可能會有十幾毫秒的誤差);而使用普通方法(遍曆加判斷)耗時卻達到了2秒多。效率提升近2000倍。
Linq的效率為什麼會這麼快呢。
這是因為Linq在上面的代碼中僅僅做了兩個工作:1、擷取資料來源,2、建立查詢;而使用普通方法不光做了這兩個工作,它還多做了執行查詢工作,因此Linq在此處運行效率奇高。 三、順延強制會有什麼影響 用代碼說話
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7 }; var enumList = list.Where(x => x > -4); foreach (var num in enumList) { Console.WriteLine(num); } Console.WriteLine("■■■■■■■■■■■■"); list.Add(-1); foreach (var num in enumList) { Console.WriteLine(num); }
此處我上下遍曆的enumList是同一個集合,儘管我在遍曆完第一次的時候改變了List,正常來講我上下兩次遍曆輸出的內容應該是一致的,然而結果卻不盡然。
剛剛提到Linq僅僅做了兩部工作:1、擷取資料來源,2、建立查詢。那麼執行查詢是在何時何處被執行的呢。答案就是:當enumList被用到的時候才會去執行,也就是說這段代碼中,var enumList = list.Where(x => x > -4);這行代碼被執行了兩次。第一次執行的時候是第一次遍曆enumList的時候,第二次執行的時候是第二次遍曆enumList的時候(大家可以打斷點觀察一下)。 四、順延強制的原理 繼續上代碼
private static void Main() { var list = new List<int> { -1, 1, 2, 3, 4, 5 }; var list1 = Delay(list); foreach (var num in list1) { Console.WriteLine(num); } list.Add(-3); list.Add(7); Console.WriteLine("■■■■■■■■■■■■"); foreach (var num in list1) { Console.WriteLine(num); } }
private static IEnumerable<int> Delay(IEnumerable<int> list) { foreach (var num in list) { if (num>0) { yield return num; } } }
其實Linq的順延強制原理很簡單,如果你理解yield return,那麼你就會明白這兩者其實原理是一模一樣的。
可以發現這段代碼跟上面的那段代碼運行模式是一樣的,當你將斷點打在var list1 = Delay(list);時,單步調試是不會進入Delay方法的,僅當你去遍曆list1的時候它才會去執行Delay方法,而且是你用幾次list1,它就會去執行幾次Delay方法。yield return與return不同之處就在於它是“按需供給”,即你什麼時候用它就什麼時候(執行)返回給你值。
之前說到順延強制可能會影響結果,那麼當我多次使用list1時,Delay這個方法就會多次執行,程式的運行效率是否會非常低呢。看一下我的測試,一萬個整形資料列印七次。 yield return(類比順延強制):
直接列印: 普通列印的已耗用時間甚至更低(因為普通列印是列印所有數字,Delay方法過濾掉負數了)。由此,可以看出:當順延強制的結果被多次調用時,並不會影響運行效率。
五、哪些Linq查詢操作符是順延強制的 轉到Linq查詢操作符的定義看一下
當傳回值為IEnumerable<TSource>、IEnumerable<IGrouping<TKey, TSource>>和IOrderedEnumerable<TSource>的時候,Linq為順延強制。實際上上述三個傳回值類型都實現了IEnumerable<T>(公開枚舉數)這個介面,yield return的傳回值就是IEnumerable<T>,所以,當Linq查詢操作符的傳回值為上述三個類型時,查詢為延遲查詢,其他為立即執行。
六、如何避免順延強制 可以看到順延強制既有好處又有一定的影響,當順延強制會對自己的程式產生影響時,如何去避免這個影響呢。很簡單,將傳回值轉換為另外一種類型即可,Linq的查詢操作符中包含了轉換操作的定義:ToArray(轉換為數組)、ToDictionary(轉換為字典)、ToList(轉換為集合),使用這些轉換運算就可以將順延強制轉換為立即執行,也就可以避免順延強制帶來的影響了。
本人的第一篇博文,初學C#不到3個月,寫得不好的地方請在評論區指出,我會及時修正。