.net 溫故知新:【6】Linq是什麼

1、什麼是Linq

關於什麼是Linq 我們先看看這段代碼。

            List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
            var linqList = list.Where(t => t < 10)              //列表中值小於10
                           .GroupBy(t => t)                     //分組
                           .Where(t => t.Count() > 1)           //分組後出現次數大於1
                           .OrderByDescending(t => t.Count())   //按照出現次數倒序
                           .Select(t => t.Key);                 //選擇值
            Console.WriteLine(string.Join(' ',linqList));


這段代碼使用Linq對List列表進行篩選、分組、排序等一系列操作展示了Linq的強大和便捷,那麼我們爲什麼需要學習Linq?可以看到這樣一堆邏輯只幾行Linq很快就可以實現,如果要我們自己實現方法去處理這個List肯定是比較繁瑣的。
Linq是什麼?如下是官方文檔對於Linq的描述:

語言集成查詢 (LINQ) 是一系列直接將查詢功能集成到 C# 語言的技術統稱。 數據查詢歷來都表示爲簡單的字符串,沒有編譯時類型檢查或 IntelliSense 支持。 此外,需要針對每種類型的數據源瞭解不同的查詢語言:SQL 數據庫、XML 文檔、各種 Web 服務等。 藉助 LINQ,查詢成爲了最高級的語言構造,就像類、方法和事件一樣。
對於編寫查詢的開發者來說,LINQ 最明顯的“語言集成”部分就是查詢表達式。 查詢表達式採用聲明性查詢語法編寫而成。 使用查詢語法,可以用最少的代碼對數據源執行篩選、排序和分組操作。 可使用相同的基本查詢表達式模式來查詢和轉換 SQL 數據庫、ADO .NET 數據集、XML 文檔和流以及 .NET 集合中的數據。

Linq的使用頻率和範圍可以說是很高很廣的,基本每天應該都會用到,那麼Linq到底是什麼呢?怎麼實現的?
要學習Linq首先需要先了解委託Lambda 表達式,因爲Linq是由 委託->Lambda->Linq 的一個變換過程。

2、委託

委託簡單來講就是指向方法的指針,就像變量是用來指向具體實現。例如String對象,我們定義一個對象string str="變量"那麼str就是指向具體實例化對象的地址,String就是類型。
按照這個思路,如果我們要定義一個指向方法的變量,委託就是爲了實現該目的。委託使用 delegate 關鍵字來聲明委託類型。
用類似於定義方法簽名的語法來定義委託類型。 只需向定義添加 delegate 關鍵字即可,如下我們定義一個比較兩個數字的委託類型。

//比較兩個數字
public delegate int Comparison(int i, int n);

接着我們定義委託變量comparison並指向方法ComparisonMax方法,該方法比較兩個int大小,返回大的一個。

委託是和類平級的應以,理應放類同級別,但是C#支持類嵌套定義,所以我們把和本類關聯性強的委託可以嵌套定義,委託變量comparison指向方法後,調用comparison(1, 2)執行委託方法並打印。
當然委託可以有返回值也可以定義void無返回值,關於委託的其它方面這裏不再贅述,這裏主要是爲了看清Linq所以淺顯的梳理下。
每次使用委託的時候我們都要定義比較麻煩,所以框架已經爲我們定義好了兩個類型,ActionFunc一個無返回值,一個有返回值,並且採用泛型定義了多個委託以滿足我們日常使用。

有了這兩個系列的委託類型,上面的方式我們也可以不定義委託直接使用Func<int,int,int> comparison = ComparisonMax;來實現。

3、Lambda

在看Lamda之前我們再看下委託方法的另外一種編寫方式,匿名方法

delegate 運算符創建一個可以轉換爲委託類型的匿名方法
如下我們直接在委託變量後面使用delegate 將參數方法體直接寫,而不用聲明其名稱的方式。


Func<int,int,int> comparison = delegate(int i,int n) { return i > n ? i : n; };
         

運行打印下結果:

從 C# 3 開始,lambda 表達式提供了一種更簡潔和富有表現力的方式來創建匿名函數。 使用 => 運算符構造 Lambda
在 lambda 表達式中,lambda 運算符 將左側的輸入參數與右側的 lambda 主體分開。

使用 Lambda 表達式來創建匿名函數。 使用 lambda 聲明運算符=>(讀作 goes to) 從其主體中分離 lambda 參數列表。 Lambda 表達式可採用以下任意一種形式:

其中第一種後面寫表達式,第二種是使用大括號{}的代碼塊作爲主體,語句 lambda 與表達式 lambda 類似,只是語句括在大括號中。
其實 表達式lambda 就是 語句lambda 在只有一行的情況下可以省略大括號和return。表達式 lambda 的主體可以包含方法調用。 不過若在表達式樹中,則不得在 Lambda 表達式中使用方法調用。表達式樹是另外一個東西,我們現在使用的ORM框架就是將lambda轉換爲sql,這個過程使用表達式樹技術,比如EF查詢中,如果我們寫一個Console.WriteLine()表達式樹是沒辦法轉換的,想一下這個調用對於sql查詢來說是沒有意義的,表達式樹以後再討論吧。

因此上面的匿名函數可以通過lambda變換爲:


Func<int,int,int> comparison = (int i,int n) =>{ return i > n ? i : n; };
         

Lambda表達式參數類型也可以省略,輸入參數類型必須全部爲顯式或全部爲隱式;否則,便會生成 CS0748 編譯器錯誤。
所以表達式還可以變換爲:


Func<int,int,int> comparison = (i,n) =>{ return i > n ? i : n; };
         

將 lambda 表達式的輸入參數括在括號中。 如果沒有參數則直接寫():Action ac = () => {Console.WriteLine();}或者Action ac = () => Console.WriteLine()
如果 lambda 表達式只有一個輸入參數,則括號是可選:Func<int,int> fun = i => {return i++;}或者Func<int,int> fun = i =>i++
關於更多的lambda知識可以參看文檔:Lambda 表達式

4、實現一個Linq

有了委託和Lambda 的知識,我們可以自己寫一個簡易的Linq實現,寫一個where吧。
我們需要擴展List類的方法,當然不用擴展方法也是可以實現,直接寫方法然後調用,但是爲了還原框架實現方式,我們模仿IEnumerable類(List 繼承至IEnumerable)。

關於擴展方法:

擴展方法使你能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。 擴展方法是一種靜態方法,但可以像擴展類型上的實例方法一樣進行調用。
擴展方法被定義爲靜態方法,但它們是通過實例方法語法進行調用的。 它們的第一個參數指定方法操作的類型。 參數前面是 this 修飾符。 僅當你使用 using 指令將命名空間顯式導入到源代碼中之後,擴展方法才位於範圍中。

  • 定義擴展方法
    public static class MyLinq
    {
        public static List<T> MyLinqWhere<T>(this List<T> list, Func<T, bool> predicate)
        {
            List<T> tempList = new List<T>();
            foreach (var item in list)
            {
                if (predicate(item))
                {
                    tempList.Add(item);
                }
            }
            return tempList;
        }
    }

List類是泛型,所以我們定義泛型MyLinqWhere 方法,第一個參數使用this關鍵字修飾,然後predicate爲一個輸入參數是T返回時bool的委託用來進行對List裏面的每一個元素進行篩選,返回的bool結果判斷是否符合要求。
我們將符合要求的元素放到一個新的List裏面最後返回該List。

  • 使用Linq方式調用自定義的where方法
     List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
      var listWhere = list.MyLinqWhere(x => x < 7);
      Console.WriteLine(string.Join(' ', listWhere));

這樣就實現了一個簡單的Linq,雖然實際的IEnumerable擴展方法裏面還有其它操作,但是通過這個過程我們知道了Linq的實現。
在IEnumerable擴展方法返回參數仍然是IEnumerable,所以可以像開始我們寫的那樣進行鏈式調用

5 Linq的另外一種寫法

在剛開始的例子中我們換另外一種寫法:

var linqList2 = from t in list
                   where t < 10
                   group t by t into t
                   where t.Count() > 1
                   orderby t.Count() descending
                   select t.Key;

輸出的結果和方法調用,使用Lambda出來的結果是一樣的。

這種方式稱爲語言集成查詢,查詢表達式採用聲明性查詢語法編寫而成。 使用查詢語法,可以用最少的代碼對數據源執行篩選、排序和分組操作。 可使用相同的基本查詢表達式模式來查詢和轉換 SQL 數據庫、ADO .NET 數據集、XML 文檔和流以及 .NET 集合中的數據。
這種寫法只是一種語法方式,或者說語法糖,在編譯階段生成的代碼和Lambda表達式生成的代碼是一致的,雖然這種方法看起來比較炫酷,但是目前大家還是比較習慣Lambda的書寫方式和閱讀,瞭解就行了,要詳細學習可以參看官方文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章