[.net 物件導向程式設計基礎] (19) LINQ基礎

yubinfeng發表於2015-06-12

[.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 程式設計

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

相關文章