linq介紹

frace發表於2022-03-16

什麼是linq

語言整合查詢 (LINQ) 是一組基於將查詢功能直接整合到 C# 語言中的技術的名稱。傳統上,對資料的查詢表示為簡單的字串,在編譯時沒有型別檢查或 IntelliSense 支援。此外,您必須為每種型別的資料來源學習不同的查詢語言:SQL 資料庫、XML 文件、各種 Web 服務等等。使用 LINQ,查詢是一流的語言結構,就像類、方法、事件一樣。您可以使用語言關鍵字和熟悉的運算子針對強型別的物件集合編寫查詢。LINQ 系列技術為物件 (LINQ to Objects)、關聯式資料庫 (LINQ to SQL) 和 XML (LINQ to XML) 提供一致的查詢體驗。

之前我們查詢集合中的資料一般會使用for或foreach語句來進行查詢,Linq 使用查詢表示式來進行查詢,Linq 表示式比之前用for或forach的方式更加簡潔,比較容易新增篩選條件。

將下列int集合整體每個元素擴大10倍

            var num = new List<int>() { 1,2,3,4,5};
            IEnumerable<int> query = num.Select(n => n * 10);
            foreach (int n in query)
                Console.WriteLine(n);

從上面的例子可以看出,linq集合在查詢是簡單了很多,並且很容易新增篩選條件。

linq原理

編譯器是如何處理這些查詢表示式的呢,為了理解好這個問題就要先解釋一下linq的底層思想。

1.序列

序列是linq的基礎。序列是通過過IEnumerable和IEnumerable<T>介面進行封裝,如果某個型別實現了IEnumerable介面,就意味著它可以被迭代訪問。序列就像一個資料的傳送帶,每次只能獲取一個,知道你不想傳了或者序列中沒有資料了。序列和其他集合資料結構(比如列表和陣列)之間最大的區別就是,當你從序列讀取資料的時候,通常不知道還有多少資料項等待讀取,或者不能訪問任意的資料項——只能是當前的這個。在你看到一個linq查詢表示式的時候,應該要想到它所涉及的序列:一開始總是存在至少一個序列,且通常在中間過程會轉換為其他序列,也可能和更多的序列連線在一起。

舉個例子,獲取成年人姓名的表示式

var names = from person in people
                    where person.Age >= 18
                    select person.Name;
foreach (var n in names)
                Console.WriteLine(n);

然後講這個表示式分解成獨立的步驟:每一個箭頭代表一個序列。每個框都代表查詢表示式的一個步驟。我們獲取他整個家庭成員(用Person物件表示)。接著經過過濾後,序列就只包含成人了(還是用Person物件表示)。而最終的結果以字串形式包含這些成人的名字。每個步驟就是得到一個序列,在序列上應用操作以生成新的序列。結果不是字串"Holly"和"Jon"——而是IEnumerable <String>,這樣,在從裡面一個接一個獲取元素的時候,將首先生成"Holly",其次得到"Jon"。

那麼,序列為什麼如此重要?這是因為它是資料處理的基礎,讓我們能夠只在需要的時候才對資料進行獲取和處理。
 
2.延遲執行
上面這個查詢表示式在被建立的時候,不會處理任何資料的,也不會訪問原始的人員列表,而是在記憶體中生成了查詢到查詢的表示式,每一個序列都是通過委託例項來表示的。只有在訪問結果IEnumerable<string>的第一個元素的時候,整個車輪才開始向前滾動。(後面有附linq的原始碼感興趣的話可以研究研究)。LINQ的這個特點稱為延遲執行,當我們使用foreach迴圈語句去列印結果的時候,表示式才開始執行,Select轉換才會為它的第一個元素呼叫Where轉換。而Where轉換會訪問列表中的第一個元素,檢查這個謂詞是否匹配(在這個例子中,是匹配的),並把這個元素返回給Select。最後,依次提取出名稱作為結果返回。

 

查詢操作並不是在查詢運算子定義的時候執行,而是在真正使用集合中的資料時才執行(如:在遍歷集合時呼叫MoveNext方法和Current檢查),再舉個簡單的例子

            var num = new List<int>();
            num.Add(1);
            IEnumerable<int> query = num.Select(n => n * 10);
            foreach (int n in query)
                Console.WriteLine(n);
            num.Add(2);
            foreach (int n in query)
                Console.WriteLine(n);

輸出的結果是10 20

 絕大部分標準的LINQ查詢運算子都具有延遲載入這種特性,但也有例外:

  •  那些返回單個元素或返回一個數值的運算子,如First或Count。
  • 轉換運算子:ToArray,ToList,ToDictonnary,ToLookup。

以上這些運算子都會觸發LINQ語句立即執行,因為它們的返回值型別不支援延遲載入。

 3.標準查詢操作符

在LINQ中存在著大量的運算,即所謂的標準查詢操作符,簡單介紹幾個常用的,這些linq的擴充套件方法原始碼附在後面可以自行研究。
1.Where
var list = query.Where(m => m.PID == corpID && m.type == 8);

2.OrderBy

var list = query.Where(m => m.PID == corpID && m.type == 8).OrderBy(m => m.PID);

3.join連線

var q2 = from u in dataContext.useinfo
                         join d in dataContext.useDetails on u.id equals d.id
                         select u;
//join時必須將join後的表into到一個新的變數XX中,然後要用XX.DefaultIfEmpty()表示外連線。
//DefaultIfEmpty使用了泛型中的default關鍵字。default關鍵字對於引用型別將返回null,而對於值型別則返回0。
 //對於結構體型別,則會根據其成員型別將它們相應地初始化為null(引用型別)或0(值型別)
var q3 = from u in dataContext.useinfo
             join d in dataContext.useDetails on u.id equals d.id into f
             from c in f.DefaultIfEmpty()
             select c;

附件:linq原始碼 https://github.com/dotnet/runtime

【參考】《深入理解C#》