1. 前言
在 LINQ 的世界裡有兩大「流派」寫查詢,兩種語法風格,乍看之下好像完全不一樣。就是 Query Syntax(查詢語法)跟 Method Syntax(方法語法)。如果你以為寫程式就是很邏輯的事,今天我們要加點藝術感,因為選語法就像選畫筆還是鉛筆:兩個都能畫圖,但一個比較適合上色,一個比較適合勾線。
為什麼要有兩種語法?這問題問得好!想像你要點咖啡。你可以說:「可以來一杯加牛奶和一匙糖的濃縮咖啡嗎?」,也可以寫在紙上:「濃縮。牛奶。1 糖」。兩種都懂,但一個比較口語,一個比較精簡。
LINQ 也是這樣。一種語法設計得很像資料庫語言 SQL,另一種則更「C# 風」也更彈性。最後 C# 編譯器都會把這兩種語法轉成一樣的東西:方法呼叫。所以選哪種,通常就是看你覺得哪個好讀、自己喜歡哪個。
我們來看看兩種寫法。
2. Method Syntax
這種寫法就是一直用點(.)串接 LINQ 的 extension methods。這種方式在 .NET 開發者裡超紅,因為很容易加新操作,結果永遠是 IEnumerable<T>,而且直接寫在程式裡很順手。
範例:篩選並排序商品
var filteredProducts = products
.Where(p => p.Price < 1000) // 依價格篩選
.OrderBy(p => p.Name) // 依名稱排序
.ToList(); // 轉成 List<Product>
Where、OrderBy 跟 ToList 都是 LINQ 的 extension methods,每個都會回傳一個新的資料集,可以繼續處理下去。
流程圖
graph LR
A[products] --> B[Where]
B --> C[OrderBy]
C --> D[ToList]
Method Syntax 的優點
- 超彈性:可以很輕鬆串很多操作。
- 全部都是 C# 方法,IDE 會自動補齊。
- LINQ 幾乎所有功能都能用(甚至更多)。
再一個例子:選出所有 18 歲以上用戶的名字
假設我們有個 User 類別:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
現在來選出成年用戶的名字:
List<User> users = ... // 某處宣告
var adultNames = users
.Where(u => u.Age >= 18)
.Select(u => u.Name)
.ToList();
3. Query Syntax(SQL 風語法)
這種語法就是為了讓 C# 開發者有 SQL 的熟悉感。它長得很像資料庫查詢,開頭是 from 關鍵字。
範例:同樣的篩選和排序
var filteredProducts =
from p in products
where p.Price < 1000
orderby p.Name
select p;
這裡讀起來就像經典查詢:「從 products 拿 p,p.Price < 1000,依 p.Name 排序,選 p」。
流程圖
flowchart TD
A[products] -->|from p in products| B[where p.Price < 1000]
B --> C[orderby p.Name]
C --> D[select p]
Query Syntax 的優點
- 很像 SQL,對有資料庫經驗的人超直覺。
- 查詢很長、有多個條件、分組、join 時更好讀(join)。
再一個例子:選出成年用戶的名字
var adultNames =
from u in users
where u.Age >= 18
select u.Name;
注意:這裡 select 後面可以直接選欄位,不一定要整個物件,比如只要名字。
4. 比較:Method Syntax vs Query Syntax
| Method Syntax | Query Syntax | |
|---|---|---|
| 語法 | |
|
| 像什麼 | 一般方法/串接 | SQL |
| 適用場合 | 任何操作都能用 | 不是所有操作都能用(像 、 只能用 Method Syntax) |
| 可讀性 | 串接時很好讀 | 分組、join 時更好讀 |
| 回傳什麼 | 通常是 |
通常是 ,但有時要 才會變成清單 |
真實世界小知識
微軟官方 LINQ 文件的範例通常兩種寫法都給。但實務上 Method Syntax 越來越常見,因為跟 extension methods(Where、Select、OrderBy 等等)搭配起來超順。
要不要混著用?怎麼選風格
可以在同一個專案(甚至同一個查詢)混用兩種語法(但這樣看起來怪怪的)。重點是不要讓程式碼變成大雜燴。通常一個模組或專案選一種風格,大家比較好讀。
Method Syntax 很適合連續轉換、用到所有 LINQ 方法(像 Sum、Count、Any 等只有方法版的)。
Query Syntax 超適合 join、group by 或多層條件的情境——這時候可讀性大勝。
5. 風格轉換:同一件事不同寫法
C# 裡的 LINQ 會把你寫的 SQL 風查詢(Query Syntax)轉成方法串接(Method Syntax)。也就是說,不管你怎麼寫,C# 編譯時都會變成 extension methods。
範例 1 — 篩選:
Query Syntax:
var adults = from u in users
where u.Age >= 18
select u;
Method Syntax(等價):
var adults = users.Where(u => u.Age >= 18);
範例 2 — 選欄位(Select):
Query Syntax:
var names = from u in users
select u.Name;
Method Syntax:
var names = users.Select(u => u.Name);
7. 分組和 join(join、group by)
建立新物件
我們來幫學習用的小程式加個功能,顯示依年齡分組的用戶清單。這時候語法差異就很明顯了。
Query Syntax(分組):
var usersByAge =
from u in users
group u by u.Age into ageGroup
select new { Age = ageGroup.Key, Users = ageGroup.ToList() };
我們把用戶依年齡分組(group u by u.Age)。into 之後會有個 ageGroup 變數,就是那個分組本身。
Method Syntax(等價):
var usersByAge = users
.GroupBy(u => u.Age)
.Select(ageGroup => new { Age = ageGroup.Key, Users = ageGroup.ToList() });
再難一點:join(join)
假設我們有訂單清單(orders)跟用戶清單(users)。我們想要查出用戶名字和他們的訂單金額。
Query Syntax:
var userOrders =
from user in users
join order in orders on user.Id equals order.UserId
select new { user.Name, order.Amount };
Method Syntax:
var userOrders = users.Join(
orders,
user => user.Id,
order => order.UserId,
(user, order) => new { user.Name, order.Amount }
);
8. 常見錯誤、陷阱和小技巧
最常見的誤會之一:如果你沒加 .ToList(),結果不是清單,而是「延遲查詢」(lazy query),只有第一次迭代時才會執行。這很方便,但如果原本的集合在查詢後有變動,可能會出現意料外的結果。延遲執行的細節我們之後會再聊(等你熟 LINQ 方法後)。
很多新手會搞混,不是所有 LINQ 方法都能用在 Query Syntax。例如不能直接寫 select sum(u.Age),要改用 Method Syntax(users.Sum(u => u.Age))。
GO TO FULL VERSION