[.net 面向對象編程基礎] (19) LINQ基礎


[.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.csSystem.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”

也就是說下面的工作沒有做。

3yield 關鍵字

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時不要試圖對被遍歷的集合進行removeadd等操作

任何集合,即使被標記為線程安全的,在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>

.NET 技術交流群:467189533    .NET 程序設計

==============================================================================================   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM