西西軟件園多重安全檢測下載網站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁西西教程數據庫教程 → Linq入門教程詳解

Linq入門教程詳解

相關軟件相關文章發(fā)表評論 來源:西西整理時間:2013/7/28 22:38:18字體大。A-A+

作者:西西點擊:29次評論:0次標簽: Linq

  • 類型:數據庫類大。1.7M語言:英文 評分:5.0
  • 標簽:
立即下載

在說LINQ之前必須先說說幾個重要的C#語言特性

一:與LINQ有關的語言特性

1.隱式類型

(1)源起

在隱式類型出現之前,

我們在聲明一個變量的時候,

總是要為一個變量指定他的類型

甚至在foreach一個集合的時候,

也要為遍歷的集合的元素,指定變量的類型

隱式類型的出現,

程序員就不用再做這個工作了。

(2)使用方法

 來看下面的代碼:

var a = 1; //int a = 1;
var b = "123";//string b = "123"; 
var myObj = new MyObj();//MyObj myObj = new MyObj()

上面的每行代碼,與每行代碼后面的注釋,起到的作用是完全一樣的

也就是說,在聲明一個變量(并且同時給它賦值)的時候,完全不用指定變量的類型,只要一個var就解決問題了

(3)你擔心這樣寫會降低性能嗎?

我可以負責任的告訴你,這樣寫不會影響性能!

上面的代碼和注釋里的代碼,編譯后產生的IL代碼(中間語言代碼)是完全一樣的

(編譯器根據變量的值,推導出變量的類型,才產生的IL代碼)

(4)這個關鍵字的好處:

你不用在聲明一個變量并給這個變量賦值的時候,寫兩次變量類型

(這一點真的為開發(fā)者節(jié)省了很多時間)

在foreach一個集合的時候,可以使用var關鍵字來代替書寫循環(huán)變量的類型

(5)注意事項

你不能用var關鍵字聲明一個變量而不給它賦值

因為編譯器無法推導出你這個變量是什么類型的。

2.匿名類型

(1)源起

創(chuàng)建一個對象,一定要先定義這個對象的類型嗎?

不一定的!

來看看這段代碼

(2)使用 

         var obj = new {Guid.Empty, myTitle = "匿名類型", myOtherParam = new int[] { 1, 2, 3, 4 } };
         Console.WriteLine(obj.Empty);//另一個對象的屬性名字,被原封不動的拷貝到匿名對象中來了。
         Console.WriteLine(obj.myTitle);
         Console.ReadKey();

new關鍵字之后就直接為對象定義了屬性,并且為這些屬性賦值

而且,對象創(chuàng)建出來之后,在創(chuàng)建對象的方法中,還可以暢通無阻的訪問對象的屬性

當把一個對象的屬性拷貝到匿名對象中時,可以不用顯示的指定屬性的名字,這時原始屬性的名字會被“拷貝”到匿名對象中

(3)注意

如果你監(jiān)視變量obj,你會發(fā)現,obj的類型是Anonymous Type類型的

不要試圖在創(chuàng)建匿名對象的方法外面去訪問對象的屬性!

(4)優(yōu)點

這個特性在網站開發(fā)中,序列化和反序列化JSON對象時很有用

3.自動屬性

(1)源起

為一個類型定義屬性,我們一般都寫如下的代碼:

    public class MyObj2
    {
        private Guid _id;
        private string _Title;
        public Guid id 
        {
            get { return _id; }
            set { _id = value; } 
        }
        public string Title
        {
            get { return _Title; }
            set { _Title = value; }
        }
    }

但很多時候,這些私有變量對我們一點用處也沒有,比如對象關系映射中的實體類。

自C#3.0引入了自動實現的屬性,

以上代碼可以寫成如下形式:

(2)使用

    public class MyObj
    {
        public Guid id { get; set; }
        public string Title { get; set; }
    }

這個特性也和var關鍵字一樣,是編譯器幫我們做了工作,不會影響性能的

4.初始化器

(1)源起

我們創(chuàng)建一個對象并給對象的屬性賦值,代碼一般寫成下面的樣子

            var myObj = new MyObj();
            myObj.id = Guid.NewGuid();
            myObj.Title = "allen";

自C#3.0引入了對象初始化器,

代碼可以寫成如下的樣子

(2)使用

var myObj1 = new MyObj() { id = Guid.NewGuid(), Title = "allen" };

如果一個對象是有參數的構造函數

那么代碼看起來就像這樣

var myObj1 = new MyObj ("allen") { id = Guid.NewGuid(), Title = "allen" };

集合初始化器的樣例代碼如下:

var arr = new List<int>() { 1, 2, 3, 4, 5, 6 };

(3)優(yōu)點

我個人認為:這個特性不是那么amazing,

這跟我的編碼習慣有關,集合初始化器也就罷了,

真的不習慣用對象初始化器初始化一個對象!

5.委托

(1)使用

我們先來看一個簡單的委托代碼

    delegate Boolean moreOrlessDelgate(int item);
    class Program
    {
        static void Main(string[] args)
        {
            var arr = new List<int>() { 1, 2, 3, 4, 5, 6,7,8 };
            var d1 = new moreOrlessDelgate(More);            
            Print(arr, d1);
            Console.WriteLine("OK");

            var d2 = new moreOrlessDelgate(Less);
            Print(arr, d2);
            Console.WriteLine("OK");
            Console.ReadKey();

        }
        static void Print(List<int> arr,moreOrlessDelgate dl)
        {
            foreach (var item in arr)
            {
                if (dl(item))
                {
                    Console.WriteLine(item);
                }
            }
        }
        static bool More(int item)
        {
            if (item > 3)
            { 
                return true; 
            }
            return false;
        }
        static bool Less(int item)
        {
            if (item < 3)
            {
                return true;
            }
            return false;
        }
    }

這段代碼中

<1>首先定義了一個委托類型

delegate Boolean moreOrlessDelgate(int item);

你看到了,委托和類是一個級別的,確實是這樣:委托是一種類型

和class標志的類型不一樣,這種類型代表某一類方法。

這一句代碼的意思是:moreOrlessDelgate這個類型代表返回值為布爾類型,輸入參數為整形的方法

<2>有類型就會有類型的實例

var d1 = new moreOrlessDelgate(More);   
var d2 = new moreOrlessDelgate(Less);

這兩句就是創(chuàng)建moreOrlessDelgate類型實例的代碼,

它們的輸入參數是兩個方法

<3>有了類型的實例,就會有操作實例的代碼 

Print(arr, d1);
Print(arr, d2);

我們把前面兩個實例傳遞給了Print方法

這個方法的第二個參數就是moreOrlessDelgate類型的

在Print方法內用如下代碼,調用委托類型實例所指向的方法

dl(item)

6.泛型

(1)為什么要有泛型

假設你是一個方法的設計者,

這個方法有一個傳入參數,有一個返回值。

但你并不知道這個參數和返回值是什么類型的,

如果沒有泛型,你可能把參數和返回值的類型都設定為Object了

那時,你心里肯定在想:反正一切都是對象,一切的基類都是Object

沒錯!你是對的!

這個方法的消費者,會把他的對象傳進來(有可能會做一次裝箱操作)

并且得到一個Object的返回值,他再把這個返回值強制類型轉化為他需要的類型

除了裝箱和類型轉化時的性能損耗外,代碼工作的很好!

那么這些新能損耗能避免掉嗎?

有泛型之后就可以了!

(2)使用

<1>使用簡單的泛型

先來看下面的代碼:

            var intList = new List<int>() { 1,2,3};
            intList.Add(4);
            intList.Insert(0, 5);
            foreach (var item in intList)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();

在上面這段代碼中我們聲明了一個存儲int類型的List容器

并循環(huán)打印出了容器里的值

注意:如果這里使用Hashtable、Queue或者Stack等非泛型的容器

就會導致裝箱操作,損耗性能。因為這些容器只能存儲Object類型的數據

<2>泛型類型

List<T>、Dictionary<TKey, TValue>等泛型類型都是.net類庫定義好并提供給我們使用的

但在實際開發(fā)中,我們也經常需要定義自己的泛型類型

來看下面的代碼:

    public static class SomethingFactory<T>
    {
        public static T InitInstance(T inObj)
        {
            if (false)//你的判斷條件
            {
                //do what you want...
                return inObj;
            }
            return default(T);
        }
    }

這段代碼的消費者如下:

            var a1 = SomethingFactory<int>.InitInstance(12);
            Console.WriteLine(a1);
            Console.ReadKey();

輸出的結果為0

這就是一個自定義的靜態(tài)泛型類型,

此類型中的靜態(tài)方法InitInstance對傳入的參數做了一個判斷

如果條件成立,則對傳入參數進行操作之后并把它返回

如果條件不成立,則返回一個空值

注意:

[1]

傳入參數必須為指定的類型,

因為我們在使用這個泛型類型的時候,已經規(guī)定好它能接收什么類型的參數

但在設計這個泛型的時候,我們并不知道使用者將傳遞什么類型的參數進來

[2]

如果你想返回T類型的空值,那么請用default(T)這種形式

因為你不知道T是值類型還是引用類型,所以別擅自用null

<3>泛型約束

很多時候我們不希望使用者太過自由

我們希望他們在使用我們設計的泛型類型時

不要很隨意的傳入任何類型

對于泛型類型的設計者來說,要求使用者傳入指定的類型是很有必要的

因為我們只有知道他傳入了什么東西,才方便對這個東西做操作

讓我們來給上面設計的泛型類型加一個泛型約束

代碼如下:

public static class SomethingFactory<T> where T:MyObj

這樣在使用SomethingFactory的時候就只能傳入MyObj類型或MyObj的派生類型啦

注意:

還可以寫成這樣

where T:MyObj,new()

來約束傳入的類型必須有一個構造函數。

(3)泛型的好處

<1>算法的重用

想想看:list類型的排序算法,對所有類型的list集合都是有用的

<2>類型安全

<3>提升性能

沒有類型轉化了,一方面保證類型安全,另一方面保證性能提升

<4>可讀性更好

這一點就不解釋了 

7.泛型委托

(1)源起

委托需要定義delgate類型

使用起來頗多不便

而且委托本就代表某一類方法

開發(fā)人員經常使用的委托基本可以歸為三類,

哪三類呢?

請看下面:

(2)使用

<1>Predicate泛型委托

把上面例子中d1和d2賦值的兩行代碼改為如下:

            //var d1 = new moreOrlessDelgate(More);
            var d1 = new Predicate<int>(More);

            //var d2 = new moreOrlessDelgate(Less);
            var d2 = new Predicate<int>(Less);

把Print方法的方法簽名改為如下:

        //static void Print(List<int> arr, moreOrlessDelgate<int> dl)
        static void Print(List<int> arr, Predicate<int> dl)

然后再運行方法,控制臺輸出的結果和原來的結果是一模一樣的。

那么Predicate到底是什么呢?

來看看他的定義:

    // 摘要:
    //     表示定義一組條件并確定指定對象是否符合這些條件的方法。
    //
    // 參數:
    //   obj:
    //     要按照由此委托表示的方法中定義的條件進行比較的對象。
    //
    // 類型參數:
    //   T:
    //     要比較的對象的類型。
    //
    // 返回結果:
    //     如果 obj 符合由此委托表示的方法中定義的條件,則為 true;否則為 false。
    public delegate bool Predicate<in T>(T obj);

看到這個定義,我們大致明白了。

.net為我們定義了一個委托,

這個委托表示的方法需要傳入一個T類型的參數,并且需要返回一個bool類型的返回值

有了它,我們就不用再定義moreOrlessDelgate委托了,

而且,我們定義的moreOrlessDelgate只能搞int類型的參數,

Predicate卻不一樣,它可以搞任意類型的參數

但它規(guī)定的還是太死了,它必須有一個返回值,而且必須是布爾類型的,同時,它必須有一個輸入參數

除了Predicate泛型委托,.net還為我們定義了Action和Func兩個泛型委托

<2>Action泛型委托

Action泛型委托限制的就不那么死了,

他代表了一類方法:

可以有0個到16個輸入參數,

輸入參數的類型是不確定的,

但不能有返回值,

來看個例子:

            var d3 = new Action(noParamNoReturnAction);
            var d4 = new Action<int, string>(twoParamNoReturnAction);

 注意:尖括號中int和string為方法的輸入參數

        static void noParamNoReturnAction()
        {
            //do what you want
        }
        static void twoParamNoReturnAction(int a, string b)
        {
            //do what you want
        }

<3>Func泛型委托

為了彌補Action泛型委托,不能返回值的不足

.net提供了Func泛型委托,

相同的是它也是最多0到16個輸入參數,參數類型由使用者確定

不同的是它規(guī)定要有一個返回值,返回值的類型也由使用者確定

如下示例:

var d5 = new Func<int, string>(oneParamOneReturnFunc);

注意:string類型(最后一個泛型類型)是方法的返回值類型

        static string oneParamOneReturnFunc(int a)
        {
            //do what you want
            return string.Empty;
        }

8.匿名方法

(1)源起

在上面的例子中

為了得到序列中較大的值

我們定義了一個More方法

var d1 = new Predicate<int>(More);

然而這個方法,沒有太多邏輯(實際編程過程中,如果邏輯較多,確實應該獨立一個方法出來)

那么能不能把More方法中的邏輯,直接寫出來呢?

C#2.0之后就可以了,

請看下面的代碼:

(2)使用

            var arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8 };
            //var d1 = new moreOrlessDelgate(More);
            //var d1 = new Predicate<int>(More);
            var d1 = new Predicate<int>(delegate(int item)
            {

//可以訪問當前上下文中的變量
Console.WriteLine(arr.Count);

                if (item > 3)

                {
                    return true;
                }
                return false;
            });
            Print(arr, d1);
            Console.WriteLine("OK");

我們傳遞了一個代碼塊給Predicate的構造函數

其實這個代碼塊就是More函數的邏輯

(3)好處

<1>代碼可讀性更好

<2>可以訪問當前上下文中的變量

這個用處非常大,

如果我們仍舊用原來的More函數

想要訪問arr變量,勢必要把arr寫成類級別的私有變量了

用匿名函數的話,就不用這么做了。

9.Lambda表達式

(1)源起

.net的設計者發(fā)現在使用匿名方法時,

仍舊有一些多余的字母或單詞的編碼工作

比如delegate關鍵字

于是進一步簡化了匿名方法的寫法

(2)使用

            List<int> arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
            arr.ForEach(new Action<int>(delegate(int a) { Console.WriteLine(a); }));
            arr.ForEach(new Action<int>(a => Console.WriteLine(a)));

 匿名方法的代碼如下:

delegate(int a) { Console.WriteLine(a); }

使用lambda表達式的代碼如下:

a => Console.WriteLine(a)

這里解釋一下這個lambda表達式

<1>

a是輸入參數,編譯器可以自動推斷出它是什么類型的,

如果沒有輸入參數,可以寫成這樣:

() => Console.WriteLine("ddd")

<2>

=>是lambda操作符

<3>

Console.WriteLine(a)是要執(zhí)行的語句。

如果是多條語句的話,可以用{}包起來。

如果需要返回值的話,可以直接寫return語句

10.擴展方法

(1)源起

如果想給一個類型增加行為,一定要通過繼承的方式實現嗎?

不一定的!

(2)使用

來看看這段代碼:

        public static void PrintString(this String val)
        {
            Console.WriteLine(val);
        }

消費這段代碼的代碼如下:

            var a = "aaa";
            a.PrintString();
            Console.ReadKey();

我想你看到擴展方法的威力了。

本來string類型沒有PrintString方法

但通過我們上面的代碼,就給string類型"擴展"了一個PrintString方法

(1)先決條件

<1>擴展方法必須在一個非嵌套、非泛型的靜態(tài)類中定義

<2>擴展方法必須是一個靜態(tài)方法

<3>擴展方法至少要有一個參數

<4>第一個參數必須附加this關鍵字作為前綴

<5>第一個參數不能有其他修飾符(比如ref或者out)

<6>第一個參數不能是指針類型

(2)注意事項

<1>跟前面提到的幾個特性一樣,擴展方法只會增加編譯器的工作,不會影響性能(用繼承的方式為一個類型增加特性反而會影響性能)

<2>如果原來的類中有一個方法,跟你的擴展方法一樣(至少用起來是一樣),那么你的擴展方法獎不會被調用,編譯器也不會提示你

<3>擴展方法太強大了,會影響架構、模式、可讀性等等等等....

11.迭代器

·(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)迭代器的優(yōu)點:

假設我們需要遍歷一個龐大的集合

只要集合中的某一個元素滿足條件

就完成了任務

你認為需要把這個龐大的集合全部加載到內存中來嗎?

當然不用(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中的解釋如下:

在迭代器塊中用于向枚舉數對象提供值或發(fā)出迭代結束信號。

也就是說,我們可以在生成迭代器的時候,來確定什么時候終結迭代邏輯

上面的代碼可以改成如下形式:

        static IEnumerable<int> GetIterator()
        {
            Console.WriteLine("迭代器返回了1");
            yield return 1;
            Console.WriteLine("迭代器返回了2");
            yield break;
            Console.WriteLine("迭代器返回了3");
            yield return 3;
        }

 (4)注意事項

<1>做foreach循環(huán)時多考慮線程安全性

在foreach時不要試圖對被遍歷的集合進行remove和add等操作

任何集合,即使被標記為線程安全的,在foreach的時候,增加項和移除項的操作都會導致異常

(我在這里犯過錯)

<2>IEnumerable接口是LINQ特性的核心接口

只有實現了IEnumerable接口的集合

才能執(zhí)行相關的LINQ操作,比如select,where等

這些操作,我們接下來會講到。

二:LINQ

1.查詢操作符

(1)源起

.net的設計者在類庫中定義了一系列的擴展方法

來方便用戶操作集合對象

這些擴展方法構成了LINQ的查詢操作符

(2)使用

這一系列的擴展方法,比如:

Where,Max,Select,Sum,Any,Average,All,Concat等

都是針對IEnumerable的對象進行擴展的

也就是說,只要實現了IEnumerable接口,就可以使用這些擴展方法

來看看這段代碼:

            List<int> arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
            var result = arr.Where(a => { return a > 3; }).Sum();
            Console.WriteLine(result);
            Console.ReadKey();

這段代碼中,用到了兩個擴展方法。

<1>

Where擴展方法,需要傳入一個Func<int,bool>類型的泛型委托

這個泛型委托,需要一個int類型的輸入參數和一個布爾類型的返回值

我們直接把a => { return a > 3; }這個lambda表達式傳遞給了Where方法

a就是int類型的輸入參數,返回a是否大于3的結果。

<2>

Sum擴展方法計算了Where擴展方法返回的集合的和。

(3)好處

上面的代碼中

arr.Where(a => { return a > 3; }).Sum();

這一句完全可以寫成如下代碼:

(from v in arr where v > 3 select v).Sum();

而且兩句代碼的執(zhí)行細節(jié)是完全一樣的

大家可以看到,第二句代碼更符合語義,更容易讀懂

第二句代碼中的where,就是我們要說的查詢操作符。

(4)標準查詢操作符說明

<1>過濾

Where

用法:arr.Where(a => { return a > 3; })

說明:找到集合中滿足指定條件的元素

OfType

用法:arr.OfType<int>()

說明:根據指定類型,篩選集合中的元素

<2>投影

Select

用法:arr.Select<int, string>(a => a.ToString());

說明:將集合中的每個元素投影的新集合中。上例中:新集合是一個IEnumerable<String>的集合

SelectMany

用法:arr.SelectMany<int, string>(a => { return new List<string>() { "a", a.ToString() }; });

說明:將序列的每個元素投影到一個序列中,最終把所有的序列合并

<3>還有很多查詢操作符,請翻MSDN,以后有時間我將另起一篇文章把這些操作符寫全。

2.查詢表達式

(1)源起

上面我們已經提到,使用查詢操作符表示的擴張方法來操作集合

雖然已經很方便了,但在可讀性和代碼的語義來考慮,仍有不足

于是就產生了查詢表達式的寫法。

雖然這很像SQL語句,但他們卻有著本質的不同。

(2)用法

from v in arr where v > 3 select v

這就是一個非常簡單的查詢表達式

(3)說明:

先看一段偽代碼:

from [type] id in source
[join [type] id in source on expr equals expr [into subGroup]]
[from [type] id in source | let id = expr | where condition]
[orderby ordering,ordering,ordering...]
select expr | group expr by key
[into id query]

<1>第一行的解釋:

type是可選的,

id是集合中的一項,

source是一個集合,

如果集合中的類型與type指定的類型不同則導致強制轉化

<2>第二行的解釋:

一個查詢表達式中可以有0個或多個join子句,

這里的source可以不等于第一句中的source

expr可以是一個表達式

[into subGroup] subGroup是一個中間變量,

它繼承自IGrouping,代表一個分組,也就是說“一對多”里的“多”

可以通過這個變量得到這一組包含的對象個數,以及這一組對象的鍵

比如:

from c in db.Customers

        join o in db.Orders on c.CustomerID
        equals o.CustomerID into orders
        select new
        {
            c.ContactName,
            OrderCount = orders.Count()
        };

<3>第三行的解釋: 

一個查詢表達式中可以有1個或多個from子句

一個查詢表達式中可以有0個或多個let子句,let子句可以創(chuàng)建一個臨時變量

比如:

        from u in users
         let number = Int32.Parse(u.Username.Substring(u.Username.Length - 1))
         where u.ID < 9 && number % 2 == 0
         select u

一個查詢表達式中可以有0個或多個where子句,where子句可以指定查詢條件

<4>第四行的解釋:

一個查詢表達式可以有0個或多個排序方式

每個排序方式以逗號分割

<5>第五行的解釋:

一個查詢表達式必須以select或者group by結束

select后跟要檢索的內容

group by 是對檢索的內容進行分組

比如:

        from p in db.Products  
        group p by p.CategoryID into g  
        select new {  g.Key, NumProducts = g.Count()}; 

<6>第六行的解釋:

最后一個into子句起到的作用是

將前面語句的結果作為后面語句操作的數據源

比如:

        from p in db.Employees
         select new
         {
             LastName = p.LastName,
             TitleOfCourtesy = p.TitleOfCourtesy
         } into EmployeesList
         orderby EmployeesList.TitleOfCourtesy ascending
         select EmployeesList;

    相關評論

    閱讀本文后您有什么感想? 已有人給出評價!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過難過
    • 5 囧
    • 3 圍觀圍觀
    • 2 無聊無聊

    熱門評論

    最新評論

    發(fā)表評論 查看所有評論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字數: 0/500 (您的評論需要經過審核才能顯示)