微軟在.NET 3.5中加入了LINQ技術,作為配套改進了C#語言,加入了Lambda表達式,擴展方法,匿名類型等新特性用以支持LINQ。微軟同時提出了要使用聲明式編程,即描述計算規則,而不是描述計算過程。
使用LINQ技術能很好地做到聲明式編程,寫出的代碼表意能力強,可讀性高,避免了以往或其他語言的代碼中充斥大量表意不明的for循環甚至多層循環的問題。不要小看for循環和Where,Select,OrderBy等擴展方法的區別,可以不通過注釋一眼就能看出代碼意圖真的很重要。當看到java代碼中一大堆的for循環,包括多層循環,又沒有注釋,必須仔細看才能了解代碼作用時,真的很頭大。個人認為LINQ是C#語言區別于其他語言的最顯著的特性,也是最大的優勢之一。
當然現在大多數主流語言都加入了Lambda表達式,從而可以使用類似于LINQ的技術,達到聲明式編程。比如Java語言在Java 8中加入了和C#幾乎一樣的Lambda表達式語法,并加入了Stream API,以達到類似于LINQ的用法。
如此可見,聲明式編程是發展趨勢,既然使用C#,就要多用LINQ,用好LINQ,用對LINQ。不要再寫一堆一堆的for循環了!
要用好LINQ,就要學好LINQ,理解其原理,機制和用法。推薦一個學習和研究LINQ的好工具LINQPad,下面是官網和官網上的截圖。
http://www.linqpad.net/

下面針對幾個關鍵點,對LINQ進行一些初步研究。有些問題可能是使用LINQ多年的人都理解得不對的。
首先看下面的程序。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace LinqResearch{ class PRogram { static void Main(string[] args) { var list = new List<int> { 2, 1, 6, 4, 3, 5, 7, 8, 10, 9 }; Console.WriteLine("list"); var list1 = list.Select(i => { Console.WriteLine("In select {0}", i); return i * i; }); Console.WriteLine("list1"); var list2 = list1.Where(i => { Console.WriteLine("In where>10 {0}", i); return i > 10; }); Console.WriteLine("list2"); var list3 = list2.Where(i => { Console.WriteLine("In where<60 {0}", i); return i < 60; }); Console.WriteLine("list3"); var list4 = list3.OrderBy(i => { Console.WriteLine("In orderby {0}", i); return i; }); Console.WriteLine("list4"); var list5 = list4.ToList(); Console.WriteLine("list5"); foreach (var i in list5) { Console.WriteLine(i); } } }}先不要看下面的運行結果,想想打印出的是什么,然后再看結果,看看和想的一樣嗎?
listlist1list2list3list4In select 2In where>10 4In select 1In where>10 1In select 6In where>10 36In where<60 36In select 4In where>10 16In where<60 16In select 3In where>10 9In select 5In where>10 25In where<60 25In select 7In where>10 49In where<60 49In select 8In where>10 64In where<60 64In select 10In where>10 100In where<60 100In select 9In where>10 81In where<60 81In orderby 36In orderby 16In orderby 25In orderby 49list516253649
為什么先打印出list 到 list4,而沒有進到Lambda里面?
這是因為LINQ是延時計算的,即只有foreach或ToList時才去做真正計算,前面的Select,Where等語句只是聲明了計算規則,而不進行計算。
這點很重要,如果不明白這點,就會寫出有BUG的代碼,如下面的程序,打印出的是1和2,而不是1。
var a = 2; var list = new List<int> { 1, 2, 3 }; var list1 = list.Where(i => i < a); a = 3; foreach (var i in list1) { Console.WriteLine(i); }后面打印出的為什么先是select和where交錯,然后是orderby,而不是先select再where,最后orderby?
這時因為Select,Where等這些擴展方法,在聲明計算規則時是有優化的(內部可能通過表達式樹等方法實現),它并不是傻傻的按照原始定義的規則,順序執行,而是以一種優化的方法計算并獲得結果。所以使用 LINQ一般會比自己寫的原始的一大堆for循環性能還高,除非花大量時間優化自己的邏輯(一般不會有這個時間)。
可以看到針對元素2和1,并沒有打印出In where<60 的行,這說明針對這兩個元素,第二個Where里的代碼并沒有執行,因為第一個Where都沒有通過。在進行完投影(Select)和篩選(Where)后,最后進行排序(OrderBy),只針對篩選后留下的元素執行OrderBy里面的計算邏輯,一點也不浪費。
上面的程序有人可能會寫成這樣。
var list = new List<int> { 2, 1, 6, 4, 3, 5, 7, 8, 10, 9 }; Console.WriteLine("list"); var list1 = list.Select(i => { Console.WriteLine("In select {0}", i); return i * i; }).ToList(); Console.WriteLine("list1"); var list2 = list1.Where(i => { Console.WriteLine("In where>10 {0}", i); return i > 10; }).ToList(); Console.WriteLine("list2"); var list3 = list2.Where(i => { Console.WriteLine("In where<60 {0}", i); return i < 60; }).ToList(); Console.WriteLine("list3"); var list4 = list3.OrderBy(i => { Console.WriteLine("In orderby {0}", i); return i; }).ToList(); Console.WriteLine("list4"); var list5 = list4.ToList(); Console.WriteLine("list5"); foreach (var i in list5) { Console.WriteLine(i); }這樣寫打印出的結果為,
listIn select 2In select 1In select 6In select 4In select 3In select 5In select 7In select 8In select 10In select 9list1In where>10 4In where>10 1In where>10 36In where>10 16In where>10 9In where>10 25In where>10 49In where>10 64In where>10 100In where>10 81list2In where<60 36In where<60 16In where<60 25In where<60 49In where<60 64In where<60 100In where<60 81list3In orderby 36In orderby 16In orderby 25In orderby 49list4list516253649
雖然也能得到正確的結果,但是卻是不合理的。因為這樣寫每步都執行計算,并放到集合中,會有很大的性能損耗,失去了使用LINQ的優勢。
何時進行真正計算是個值得思考的問題,多了會增加中間集合的數量,性能不好,少了有可能會有多次重復計算,性能也不好。下文會有說明。
如果使用Resharper插件,會提示出重復迭代(可能會有多次重復計算)的地方,這個功能很好,便于大家分析是否存在問題。
使用Max和Min要小心,Max和Min等聚合運算需要集合中存在值,否則會拋出異常,筆者多次遇到這個問題產生的BUG。
當前面有Where篩選時,后面使用Max或Min不一定是安全的,如下面的代碼會拋出異常。
var a = 0; var list = new List<int> { 1, 2, 3 }; var min = list.Where(i => i < a).Min(); Console.WriteLine(min);如果a來源于外部值,又有大段的邏輯,這樣的BUG不易發現。
解決方法有多種,我們來分析一下,一種方法是可以先調一下Any,再使用Min,代碼如下,
var a = 0; var list = new List<int> { 1, 2, 3 }; var list2 = list.Where(i => i < a); var min = 0; if (list2.Any()) { min = list2.Min(); } Console.WriteLine(min);把代碼改為如下,
var a = 3; var list = new List<int> { 1, 2, 3 }; var list2 = list.Where(i => { Console.WriteLine("In where {0}", i); return i < a; }); var min = 0; if (list2.Any(i => { Console.WriteLine("In any {0}", i); return true; })) { min = list2.Min(); } Console
新聞熱點
疑難解答