[.net 物件導向程式設計基礎] (19) LINQ基礎
上兩節我們介紹了.net的陣列、集合和泛型。我們說到,陣列是從以前程式語言延伸過來的一種引用型別,採用事先定義長度分配儲存區域的方式。而集合是.Net 版本初期的用於解決資料集檢索方便而設計的,它比陣列的優勢除了檢索方便之外,還可以在使用過程中自動分配儲存區域,不需要事先定義大小。但是集合存在型別不安全以及頻繁裝箱、拆箱操作帶來的效能問題。泛型是.net 2.0以後為了解決集合的缺陷而設計的,採用例項呼叫階段再宣告型別的方法,即解決了安全問題,也解決了效率問題。
隨著.net 3.5以後版本的發展,微軟設計了LINQ,讓我們檢索更加方便,開發更加高效了。
1.LINQ概念
LINQ,語言整合查詢(Language Integrated Query)是一組用於c#和Visual Basic語言的擴充套件。它允許編寫C#或者Visual Basic程式碼以查詢資料庫相同的方式操作記憶體資料。
以上是來自百度百科的定義。
通過英文名描述,我們可以看出,它就是一種讓開發者像查詢資料庫一樣,去檢索記憶體資料的方案。
2.LINQ學習之前需要掌握以下知識點
看著有點多,但是都是學好LINQ的基礎,我們說了LINQ就是像SQL語句一樣操作記憶體資料,那麼學習SQL要掌握表,欄位,檢視的基礎概念,LINQ也一樣。
2.1 隱式型別
之前我們定義變數時,需要指定一個型別,foreach遍歷時,也要指定型別。隱式型別的出現我們不再需要做這個工作了,而使用var 定義就可以了,這點更像弱型別語言比如javascipt。但是.NET並不是弱型別語言,隱式型別的定義寫法如下:
var i=1; var b=”xyz”; var obj=new MyObj();
以上隱式型別等效於
int i=1; string b=”xyz”; MyObj obj=new MyObj();
關於隱式型別的幾點說明:
A.有了隱式型別,可以編碼中使用var定義任意型別變數
B.隱式型別並不會影響程式效能,.NET會在編譯時幫我們轉換成具體的資料型別,因此我們必須宣告隱式型別時賦值,不然.NET 不能判斷具體轉成什麼型別而報錯。
C.隱式型別var 使我們開發節約了不少時間。定義一個變數時,型別需要輸兩次,var 一下就搞定了。在foreach遍歷的時候也可 以使用var來書寫迴圈變數的型別。
2.2 匿名型別
我們new一個物件,它裡面的元素,型別可以不申明,而使用匿名方式。如下:
//匿名型別 var obj =new { str = "abc", Guid.Empty,myArray = new int[] { 1, 2, 3 } }; Console.WriteLine(obj.str); Console.WriteLine(obj.Empty); Console.WriteLine(obj.myArray[0]); Console.ReadLine();
執行結果如下:
我們看到new 一個物件後,自動為物件定義了屬性,併為這些屬性賦值,當把一個物件的屬性拷貝到匿名物件中時,可以不用顯示的指定屬性的名字,這時原始屬性的名字會被“拷貝”到匿名物件中.如下圖:
自動建立了obj.Empty屬性名。
如果你監視變數obj,你會發現,obj的型別是Anonymous Type型別的。
不要試圖在建立匿名物件的方法外面去訪問物件的屬性!
這個特性在網站開發中,序列化和反序列化JSON物件時很有用。
2.3 自動屬性
記得在“重構”一節中,說了屬性的重構,只需要欄位名上使用vs.net提供的“封裝欄位”,就為我們生成了相對應的屬性,如下圖:
(圖1)
(圖2)
(圖3)
自.net 3.0以後,我們程式碼可以寫的更簡單了,程式碼如下:
//自動屬性 class Flower { public string Leaf{get;set;} public string Name{get;set;} }
重構當然只是.net的輔助功能,對於自動屬性小夥伴們肯定會擔心這樣寫的效率,這點完全可以放心,跟var 隱式型別一樣,.net在編譯的時候,會幫我們寫完整的,沒有任何效能問題。
2.4 初始化器
對於一個物件的初始化,假如有以下兩個物件:
//初始化器 class Flower { public string Leaf{get;set;} public string Name{get;set;} } class Bear { public Bear(string name){} public string Name { get; set; } public double Weight { get; set; } }
在3.0以前的版中,我們一般類似於如下寫法:
//初始化器 Flower flower = new Flower(); flower.Leaf = "葉子"; flower.Name = "棉花";
在3.0以後的版本中,我們可以這樣寫:
//.net 3.0以後物件初始化可以如下寫法 Flower flower2 = new Flower() { Name = "桃花", Leaf = "桃葉" }; //建構函式帶引數物件初始化 Bear bear = new Bear("北極熊"){ Name="熊熊", Weight=300}; //集合初始化 var array = new List<char>() { 'a', 'b','c', 'd', 'e','f' };
我們可以看出,在寫法上簡潔好多了。特別是對泛型集合的賦值,相當簡潔明瞭。
2.5 匿名方法
說到匿名方法,就是說在委託一個方法的時候,可以把方法寫在委託宣告的時候,簡單說就是不用把方法單獨寫出來。在說這個之前,我們本來是要介紹一下委託的,但是下一節,我們會重點說明委託,在這裡只需要瞭解匿名方法就可以了,下面看一個例子:
1 // 委託函式 2 Func<int, int, string> func1 = Adds; 3 // 匿名方法 4 Func<int, int, string> func2 = 5 delegate(int a, int b) 6 { 7 return a+"+"+b+"等於幾?" + Environment.NewLine +(a+b).ToString(); 8 }; 9 // Lambda表示式 10 Func<int, int, string> func3 = 11 (a, b) => { return a + "+" + b + "等於幾?" + Environment.NewLine + (a + b).ToString(); }; 12 13 // 呼叫Func委託 14 string sum = func2(45, 36); 15 16 Console.WriteLine(sum); 17 Console.ReadLine();
//委託方法 static string Adds(int a, int b) { return a + "+" + b + "等於幾?" + Environment.NewLine + (a + b).ToString(); }
通過使用匿名方法,可以訪問上下文中的變數,在方法本身不是很長的情況下,輕量級的寫法,非常實用。
這點在下一節委託中具體說明,小夥伴在這裡沒看明白也沒關係。
2.6 擴充套件方法
1) 擴充套件方法宣告在靜態類中,定義為一個靜態方法,其第一個引數需要使用this關鍵字標識,指示它所擴充套件的型別。
2) 擴充套件方法可以將方法寫入最初沒有提供該方法的類中。還可以把方法新增到實現某個介面的任何類中,這樣多個類就可以使用相同的實現程式碼。(LINQ中,System.Linq.Queryable.cs和System.Linq.Enumerable.cs 正是對介面新增擴充套件方法)
3) 擴充套件方法雖定義為一個靜態方法,但其呼叫時不必提供定義靜態方法的類名,只需引入對應的名稱空間,訪問方式同例項方法。
4) 擴充套件方法不能訪問它所擴充套件的型別的私有成員。
例子:
public static IEnumerable<TSource> MyWhere<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate) { foreach (TSource item in source) { if (predicate(item)) yield return item; } }
我們再看一下擴充套件方法的厲害的地方,要給一個型別增加行為,不一定要使用繼承的方法實現,還可以這樣寫:
var a = "aaa"; a.PrintString(); Console.ReadKey();
但通過我們上面的程式碼,就給string型別"擴充套件"了一個PrintString方法。
(1)先決條件
<1>擴充套件方法必須在一個非巢狀、非泛型的靜態類中定義
<2>擴充套件方法必須是一個靜態方法
<3>擴充套件方法至少要有一個引數
<4>第一個引數必須附加this關鍵字作為字首
<5>第一個引數不能有其他修飾符(比如ref或者out)
<6>第一個引數不能是指標型別
(2)注意事項
<1>跟前面提到的幾個特性一樣,擴充套件方法只會增加編譯器的工作,不會影響效能(用繼承的方式為一個型別增加特性反而會影響效能)
<2>如果原來的類中有一個方法,跟你的擴充套件方法一樣(至少用起來是一樣),那麼你的擴充套件方法獎不會被呼叫,編譯器也不會提示你
<3>擴充套件方法太強大了,會影響架構、模式、可讀性等等等等....
2.7 迭代器
(1)使用
我們每次針對集合型別編寫foreach程式碼塊,都是在使用迭代器
這些集合型別都實現了IEnumerable介面
都有一個GetEnumerator方法
但對於陣列型別就不是這樣
編譯器把針對陣列型別的foreach程式碼塊
替換成了for程式碼塊。
來看看List的型別簽名:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
IEnumerable介面,只定義了一個方法就是:
IEnumerator<T> GetEnumerator();
(2)迭代器的優點:
假設我們需要遍歷一個龐大的集合
只要集合中的某一個元素滿足條件
就完成了任務
你認為需要把這個龐大的集合全部載入到記憶體中來嗎?
當然不用(C#3.0之後就不用了)!
來看看這段程式碼:
static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield return 2; Console.WriteLine("迭代器返回了3"); yield return 3; }
foreach (var i in GetIterator()) { if (i == 2) { break; } Console.WriteLine(i); } Console.ReadKey();
輸出結果為:
迭代器返回了1 1 迭代器返回了2
大家可以看到:
當迭代器返回2之後,foreach就退出了
並沒有輸出“迭代器返回了3”
也就是說下面的工作沒有做。
(3)yield 關鍵字
MSDN中的解釋如下:
在迭代器塊中用於向列舉數物件提供值或發出迭代結束訊號。
也就是說,我們可以在生成迭代器的時候,來確定什麼時候終結迭代邏輯
上面的程式碼可以改成如下形式:
static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield break; Console.WriteLine("迭代器返回了3"); yield return 3; }
(4)注意事項
<1>做foreach迴圈時多考慮執行緒安全性
在foreach時不要試圖對被遍歷的集合進行remove和add等操作
任何集合,即使被標記為執行緒安全的,在foreach的時候,增加項和移除項的操作都會導致異常
<2>IEnumerable介面是LINQ特性的核心介面
只有實現了IEnumerable介面的集合
才能執行相關的LINQ操作,比如select,where等
關於LINQ的具體操作,下一節繼承。
2.8 Lambda表示式
Lambda表示式只是用更簡單的方式來書寫匿名方法,從而徹底簡化.NET委託型別的使用。
Lambda表示式在C#中的寫法是“arg-list => expr-body”,“=>”符號左邊為表示式的引數列表,右邊則是表示式體(body)。引數列表可以包含0到多個引數,引數之間使用逗號分割。
通過上面匿名方法的例子,我們可以看到,下面兩段程式碼是等效的:
// 匿名方法 Func<int, int, string> func2 = delegate(int a, int b) { return a+"+"+b+"等於幾?" + Environment.NewLine +(a+b).ToString(); }; // Lambda表示式 Func<int, int, string> func3 = (a, b) => { return a + "+" + b + "等於幾?" + Environment.NewLine + (a + b).ToString(); };
Lambda表示式基於數學中的λ(希臘第11個字母)演算得名,而“Lambda 表示式”(lambda expression)是指用一種簡單的方法書寫匿名方法。
3.要點:
A.LINQ,語言整合查詢(Language Integrated Query)是一種語言擴充套件,讓我們像查詢資料庫一樣去操作記憶體資料(集合等).
B.匿名型別:使用new宣告匿名型別時,不需指明型別,.NET 3.0以後版本,可以自動完成型別指定和指定屬性名稱
C.自動屬性:.net 3.0 以後版本中,我們定義屬性,只需要簡單書寫get;set;就可以了,.net在編譯階段會幫助我們完成書寫,不會存在效能問題。
D.初始化器:在3.0以後版本中,可以更加簡單的書寫物件初始化,特別是對泛型集合的賦值,相當簡潔明瞭。
E.匿名方法:在委託方法時,無需寫明方法名稱.使用匿名方法可以訪問上下文中的變數,在方法程式碼較少的情況下,輕量級實現委託,非常實用。
F.擴充套件方法:可以在不使用繼承的方式給一個型別增加行為
G.迭代器:在遍歷一個龐大集合時,不需要將它全部載入於是記憶體中,只要滿足條件就可以返回了。
H.Lambda表示式:Lambda表示式基於數學中的λ(希臘第11個字母)演算得名,而“Lambda 表示式”(lambda expression)是指用一種簡單的方法書寫匿名方法。
備註:本文參考了博友們的一些文章,數目較多,不一一列舉,在此表示感謝。
對於LINQ的使用我們下一節詳細說明。
==============================================================================================
<如果對你有幫助,記得點一下推薦哦,如有有不明白或錯誤之處,請多交流>
<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>
==============================================================================================