到現(xiàn)在為止你還未觸碰LINQ,,那進(jìn)來(lái)吧 —— LINQ入門(中篇)前 言在上篇中簡(jiǎn)單的分享了LINQ的基礎(chǔ)概念及基礎(chǔ)語(yǔ)法,如果沒(méi)有閱讀過(guò)上篇的朋友可以點(diǎn)擊這里,。感謝大家的支持,,本篇我們將更進(jìn)一步的學(xué)習(xí)LINQ的一些相關(guān)特性及應(yīng)用方法。廢話不多說(shuō),,請(qǐng)往下閱讀吧,。 延遲加載在上篇中簡(jiǎn)單的和大家提到了LINQ具有一個(gè)很有意思的特性那就是“延遲加載”(或“延遲計(jì)算”),什么是延遲加載呢,?先看來(lái)自官方的描述:延遲執(zhí)行意味著表達(dá)式的計(jì)算延遲,,直到真正需要它的實(shí)現(xiàn)值為止。是不是覺(jué)得有點(diǎn)生澀難理解呢,?按照我個(gè)人的理解通俗的講就是,,每當(dāng)我們編寫(xiě)好一段LINQ表達(dá)式時(shí),此時(shí)這個(gè)表達(dá)式所代表的序列變量?jī)H僅只是一個(gè)代理,,編譯器在執(zhí)行編譯時(shí)根本就不鳥(niǎo)這段代碼,,檢查完語(yǔ)法正確性后直接跳過(guò),直到代碼在編譯器動(dòng)態(tài)運(yùn)行序列變量在其他代碼塊被調(diào)用時(shí),它所代理的linq表達(dá)式才會(huì)執(zhí)行,。啊~~看到這里你是不是要暈了,,到底要怎么理解啊,無(wú)廢話上代碼:
1 // 已知一個(gè)序列 2 var array = new int[] {1, 2, 3}; 3 4 // 編寫(xiě)一段LINQ表達(dá)式獲得一個(gè)序列變量query 5 // 注意,,這個(gè)變量?jī)H僅是一個(gè)代理,,在執(zhí)行編譯的時(shí)候,編譯器檢查完 6 // 該代碼的正確性后直接跳過(guò),,不再理會(huì) 7 var query = from arr in array 8 where arr > 1 9 select arr; 10 11 // 調(diào)用上述序列變量query,,此時(shí)上述的LINQ表達(dá)才會(huì)執(zhí)行。注意此時(shí)已是在 12 // 編譯器Runtime 的情況下執(zhí)行 13 foreach(var q in query) 14 Console.WriteLine(q.ToString());
如果你覺(jué)得上述例子不能讓你有個(gè)深刻的理解,,那么請(qǐng)看來(lái)自MSDN的例子 1 public static class LocalExtensions 2 { 3 public static IEnumerable<string> 4 ConvertCollectionToUpperCase(this IEnumerable<string> source) 5 { 6 foreach (string str in source) 7 { 8 Console.WriteLine("ToUpper: source {0}", str); 9 yield return str.ToUpper(); 10 } 11 } 12 } 13 14 class Program 15 { 16 static void Main(string[] args) 17 { 18 string[] stringArray = { "abc", "def", "ghi" }; 19 // 這里方法 ConvertCollectionToUpperCase 是不會(huì)在編譯時(shí)進(jìn)行調(diào)用核查的,,直到下面的foreach調(diào)用變量 q 此方法才會(huì)執(zhí)行 20 var q = from str in stringArray.ConvertCollectionToUpperCase() 21 select str; 22 23 foreach (string str in q) 24 Console.WriteLine("Main: str {0}", str); 25 } 注意,ConvertCollectionToUpperCase 是一個(gè)靜態(tài)擴(kuò)展方法,,后續(xù)講解,,如果你對(duì).net 2.0 的 yeild 不熟悉的網(wǎng)上查閱吧,這里就不做介紹了,。 // 輸出結(jié)果 // ToUpper: source abc // Main: str ABC // ToUpper: source def // Main: str DEF // ToUpper: source ghi // Main: str GHI 小結(jié),,延遲加載有好也有壞,由于是在Runtime的情況下執(zhí)行序列,,所以就容易造成未知異常,,斷點(diǎn)打錯(cuò)等等,所以編碼LINQ是一定要考慮到它的這個(gè)特性,。 lambda 表達(dá)式了解完延遲加載后,,那么現(xiàn)在我們需要簡(jiǎn)單的學(xué)習(xí)一下.net 3.5 給我們帶來(lái)的新特性lambda表達(dá)式,在上篇的評(píng)論中,,有園友問(wèn)lambda和linq有什么關(guān)系,,在這里其實(shí)他們沒(méi)有任何關(guān)系,是完全不同的東西,,但是我們?yōu)槭裁匆莆账??因?yàn)樵诤罄m(xù)的學(xué)習(xí)中會(huì)使用大量的lambda表達(dá),他可以使我們的代碼更優(yōu)雅更有可讀性,,大大提高了我們的編碼效率,。 那么在學(xué)習(xí)lambda之前,先來(lái)回顧一下.net 2.0給我們帶來(lái)的委托 delegate ,,這個(gè)你一定不會(huì)感到陌生吧,,而且一定會(huì)常用他。對(duì)于委托這里就不做詳細(xì)的介紹了,,要復(fù)習(xí)委托的在網(wǎng)上查閱吧,。通過(guò)委托,,我們可以得到一個(gè)東西“匿名方法”。咦,,是不是覺(jué)得很眼熟,呵呵,,用代碼來(lái)加深回憶吧 public delegate void SomeDelegate1; public delegate void SomeDelegate2(arg 1, arg 2); // 匿名方法 SomeDelegate1 del1 += delegate() {...}; SomeDelegate2 del2 += delegate(arg1, arg2) {...} 上面的代碼中我們看到在.net 2.0時(shí)代,,我們可以通過(guò)delegate創(chuàng)建匿名方法,提高編碼的靈活性,,那么lambda和這個(gè)有什么關(guān)系呢,,lambda對(duì)匿名方法進(jìn)行了升華??创a: public delegate void SomeDelegate1; public delegate void SomeDelegate2(arg 1, arg 2); // 匿名方法 SomeDelegate1 del1 += () => {...}; SomeDelegate2 del2 += (arg1, arg2) => {...} 呵呵,,是不是覺(jué)得有點(diǎn)不可思議呢,言歸正傳什么是lambda表達(dá)式呢,,來(lái)自官方的定義:“Lambda 表達(dá)式”是一個(gè)匿名函數(shù),,它可以包含表達(dá)式和語(yǔ)句,并且可用于創(chuàng)建委托或表達(dá)式樹(shù)類型,。所有 Lambda 表達(dá)式都使用 Lambda 運(yùn)算符 =>,,該運(yùn)算符讀為“goes to”。 該 Lambda 運(yùn)算符的左邊是輸入?yún)?shù)(如果有),,右邊包含表達(dá)式或語(yǔ)句塊,。 Lambda 表達(dá)式 x => x * x 讀作“x goes to x times x”。在定義里提到了表達(dá)式樹(shù),,這是高階晉級(jí)的話題,,這里就不做討論了,我們先把精力放在入門與實(shí)戰(zhàn)應(yīng)用上,。 常規(guī)的lambda表達(dá)式如下:
(parameters) => {expression}
當(dāng)指定的委托類型沒(méi)有參數(shù)是表達(dá)式可以如下
() => {expression} 例:() => {/*執(zhí)行某些方法*/}
如果表達(dá)右側(cè)花括號(hào)里只有一個(gè)表達(dá)例如一元表達(dá)式,,二元表達(dá)式等等,又或者是一個(gè)方法時(shí)那么花括號(hào)可以省略如下: (x) => x; // 最簡(jiǎn)表達(dá)式 (x, y) => x == y; () => SomeMethod(); 注意,,如果右側(cè)的表達(dá)式存在花括號(hào)"{}",,而且委托是具有返回類型的,那么表達(dá)式必須帶上 return 關(guān)鍵字,,如下: (x, y) => {return x == y;}; 到此我們已對(duì)lambad 表達(dá)式有了一定的掌握與了解,。那么我們擴(kuò)展一下,在.net 3.5中,,ms 給我們提供了兩個(gè)泛型委托 分別是 Fun<T> 和 Action <T> 他們可以幫助我們省去了返回創(chuàng)建常用委托的麻煩,,提高編碼效率。 共同點(diǎn):它們至多提供委托傳遞6個(gè)參數(shù)(任意類型),; 不同點(diǎn):Fun 要求必須具有返回類型,,而Action則必須不返回類型,,規(guī)定返回 void 示例: Fun<int, int, bool> fun = (a, b) => a==b; Action<string> action = (p) => Console.Write(p); 小結(jié),lambda 對(duì)我個(gè)人而言是個(gè)又愛(ài)又恨啊,,不過(guò)愛(ài)多一點(diǎn),,它使我們寫(xiě)更少的代碼做更多的事,但是在調(diào)試時(shí)一旦修改表達(dá)式內(nèi)容,,那么當(dāng)前調(diào)試要么停止,,要么重新開(kāi)始,ms要是在這方面做得更完美些就好啦,。不過(guò)它也間接提醒我們要有好的編碼設(shè)計(jì)思維,。 靜態(tài)擴(kuò)展方法說(shuō)完lambda,那么我們就進(jìn)一步了解一下.net 3.5的另一個(gè)新特性“靜態(tài)擴(kuò)展方法”,,什么是靜態(tài)擴(kuò)展方法呢,,官方定義:擴(kuò)展方法使您能夠向現(xiàn)有類型“添加”方法,而無(wú)需創(chuàng)建新的派生類型,、重新編譯或以其他方式修改原始類型,。 擴(kuò)展方法是一種特殊的靜態(tài)方法,但可以像擴(kuò)展類型上的實(shí)例方法一樣進(jìn)行調(diào)用,。簡(jiǎn)單的說(shuō)就是我們可以向一個(gè)已知的類型在不通過(guò)繼承,,復(fù)寫(xiě)等操作的情況下添加一個(gè)方法,以便類型的實(shí)例可以直接使用該方法,。示例如下:
static class A { // 這里的p1,p2 僅作為示例,,實(shí)際中我們不一定需要 public static int ExtendMethod1(this string input,string p1, string p2) { return int.Parse(input + p1 + p2); } // 泛型方法 public static TOutput, ExtendMethod2<TOutput>(this object obj); { return (TOutput)obj; } }
注意,方法的 static 是必須的,而且需在靜態(tài)類里,。第一個(gè)參數(shù) this 是必須的,,緊跟著 this 后面的需要擴(kuò)展的類型實(shí)例參數(shù),也是必須的,。至于后面的方法調(diào)用傳遞參數(shù)就因個(gè)人所需了,。 既然我們學(xué)習(xí)了靜態(tài)擴(kuò)展方法,那么它和LINQ又有什么關(guān)系呢,?在System.Linq的命名空間中提供了大量的靜態(tài)擴(kuò)展方法,,這些靜態(tài)方法本身就對(duì)linq表達(dá)式的封裝,這樣我們就可以省去了編寫(xiě)簡(jiǎn)單的linq表達(dá)式的步驟,。如下:
var array = new int[]{1,2,3,4,5}; var query1 = from arr in array select arr; var query2 = array.Select(e => e);
上面的示例中 query1 和 query2 是等價(jià)的,,通過(guò) query2 我們是不是又可以偷懶了很多,呵呵,。 再來(lái)點(diǎn)帶where的 var array = new int[]{1,2,3,4,5}; var query1 = from arr in array where arr > 2 select arr; var query2 = array.Where(e => e > 2); 再來(lái)一個(gè)復(fù)合型的 var array = new int[]{1,2,3,4,5}; var max = (from arr in array.Where(e => e > 2) select arr).Max(); 是不是覺(jué)得很cool,。由于篇幅的關(guān)系在這里就不逐一的去接受這些靜態(tài)方法了,下面是一些常用的靜態(tài)方法列表,,感興趣的去MSDN查閱詳細(xì)吧,。 Aggregate , All , Any , AsEnumerable , Average , Cast , Concat , Contains, Count, DefaultIfEmpty , Distinct , ElementAt, ElementAtOrDefault ,Empty , Except , First, FirstOrDefault , GroupBy , GroupJoin , Intersect , Join , Last , LastOrDefault , LongCount , Max , Min , OfType ,OrderBy ,OrderByDescending , Range , Repeat , Reverse , Select , SelectMany , SequenceEqual , Single , SingleOrDefault , Skip , SkipWhile , Sum ,Take, TakeWhile , ThenBy , ThenByDescending , ToArray , ToDictionary , ToList, ToLookup, Union,,Where Cast<T> 和 OfType<T> 靜態(tài)擴(kuò)展方法在最后我們還是要注意兩個(gè)常用的靜態(tài)方法Cast<T>, OfType<T> 。它們的共同點(diǎn)是都能把非IEnumerable<T> 類型的集合轉(zhuǎn)換成IEnumerable<T>類型,,然后再 進(jìn)行LINQ操作,,如下 var dt = new DataTable(); dt.Columsn.Add("A", typeof(int)); var newRow1 = dt.NewRow(); newRow1["A"] = 1; var newRow2 = dt.NewRow(); newRow2["A"] = 2; dt.Rows.Add(newRow1); dt.Rows.Add(newRow2); var query1 = dt.Rows.Cast<DataRow>().Select(e=>(int)e["A"]); var query2 = dt.Rows.OfType<DataRow>().Select(e=>(int)e["A"]); 這樣我們就可以得到看上去兩個(gè)相同的序列,在這里要注意:MSDN上的說(shuō)明存在誤導(dǎo),,MSDN對(duì)于OfType<T>的解釋存在偏差,,實(shí)際上經(jīng)本人反復(fù)敲代碼驗(yàn)證,得到的結(jié)論是,,Cast對(duì)序列進(jìn)行強(qiáng)制轉(zhuǎn)換,,一旦轉(zhuǎn)換不成功則拋出異常,。OfType則是一旦轉(zhuǎn)換不成功,,則不會(huì)拋出異常,但是將會(huì)得到一個(gè)空序列,。見(jiàn)下面代碼: var arr1 = new string[] { "1","2","test" }; var arr2 = arr1.Cast<int>(); var arr3 = arr1.OfType<int>(); //通過(guò)Cast轉(zhuǎn)換,,則會(huì)拋出異常 foreach (var i in arr2) Console.WriteLine(i.ToString()); //通過(guò)OfType轉(zhuǎn)換,有異常但是不會(huì)拋出并得到一個(gè)空序列 foreach (var i in arr3) Console.WriteLine(i.ToString()); Console.Read();
總 結(jié)本文到此,,我們已對(duì)LINQ涉及的應(yīng)用有了進(jìn)一步的了解,。學(xué)習(xí)什么是linq的延遲加載,lambda和linq是否有曖昧關(guān)系,。以及靜態(tài)擴(kuò)展方法對(duì)linq的輔助作用,。也許你會(huì)問(wèn)既然可以用靜態(tài)擴(kuò)展方法替代編寫(xiě)linq,那么二者怎么擇取呢,,據(jù)磚家叫獸提議我們應(yīng)該先以linq命名空間下的靜態(tài)擴(kuò)展方法為主,,實(shí)在是很復(fù)雜的linq表達(dá)式,我們?cè)倏紤]使用linq本身的表達(dá)式編寫(xiě),。后續(xù)我們將分享學(xué)習(xí)LINQ更貼近實(shí)戰(zhàn)應(yīng)用的知識(shí),,linq to dataset, linq to xml, linq to sql, linq to entities. 感謝您的閱讀,如果有說(shuō)得不對(duì)的地方請(qǐng)指正,。 |
|