[.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>
==============================================================================================