🔍 LINQ 查詢語言
📌 什麼是 LINQ?
LINQ(Language Integrated Query)就像是 C# 裡的 SQL。 它讓你可以用簡潔的語法對集合(陣列、List 等)進行查詢、篩選、排序。
// 想像你有一堆學生成績,要找出及格的人
int[] scores = { 85, 42, 97, 63, 28, 74, 91, 55 }; // 成績陣列
// 不用 LINQ 的寫法(傳統迴圈)
List<int> passingOld = new List<int>(); // 建立新 List
foreach (int score in scores) // 走訪每個成績
{
if (score >= 60) // 如果及格
{
passingOld.Add(score); // 加入 List
}
}
// 用 LINQ 的寫法(一行搞定!)
var passing = scores.Where(s => s >= 60).ToList(); // 篩選出 >= 60 的成績
// passing = [85, 97, 63, 74, 91, 55]
📌 方法語法 vs 查詢語法
var students = new List<(string Name, int Score)> // 學生清單(用 Tuple)
{
("小明", 85), // 小明 85 分
("小美", 92), // 小美 92 分
("小華", 68), // 小華 68 分
("小強", 45), // 小強 45 分
("小芳", 78), // 小芳 78 分
};
// 方法語法(Method Syntax)— 最常用
var topStudents = students
.Where(s => s.Score >= 70) // 篩選 70 分以上
.OrderByDescending(s => s.Score) // 依分數降冪排序
.Select(s => s.Name) // 只取名字
.ToList(); // 轉成 List
// topStudents = ["小美", "小明", "小芳"]
// 查詢語法(Query Syntax)— 像 SQL
var topStudents2 =
(from s in students // 從 students 中
where s.Score >= 70 // 篩選 70 分以上
orderby s.Score descending // 依分數降冪排序
select s.Name) // 只取名字
.ToList(); // 轉成 List
// 結果和方法語法一樣
📌 Where — 篩選
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 1 到 10
// 篩選偶數
var evens = numbers.Where(n => n % 2 == 0); // n 除以 2 餘 0 就是偶數
// evens = [2, 4, 6, 8, 10]
// 篩選大於 5 的數
var big = numbers.Where(n => n > 5); // n 大於 5
// big = [6, 7, 8, 9, 10]
// 多條件篩選
var filtered = numbers.Where(n => n > 3 && n < 8); // 3 < n < 8
// filtered = [4, 5, 6, 7]
📌 Select — 轉換
int[] numbers = { 1, 2, 3, 4, 5 }; // 原始數字
// 每個數字乘以 2
var doubled = numbers.Select(n => n * 2); // 轉換每個元素
// doubled = [2, 4, 6, 8, 10]
// 轉換成字串
var strings = numbers.Select(n => $"數字 {n}"); // 轉換成描述文字
// strings = ["數字 1", "數字 2", ...]
// 轉換成匿名物件
var objects = numbers.Select(n => new // 轉換成物件
{
Value = n, // 原始值
Square = n * n, // 平方
IsEven = n % 2 == 0 // 是否為偶數
});
foreach (var obj in objects) // 走訪每個物件
{
Console.WriteLine($"{obj.Value}: 平方={obj.Square}, 偶數={obj.IsEven}");
}
📌 OrderBy / GroupBy / Join
var products = new List<(string Name, string Category, decimal Price)>
{
("蘋果", "水果", 30), // 水果類
("牛奶", "飲料", 45), // 飲料類
("香蕉", "水果", 20), // 水果類
("咖啡", "飲料", 60), // 飲料類
("橘子", "水果", 25), // 水果類
};
// OrderBy — 排序
var sorted = products.OrderBy(p => p.Price); // 依價格升冪排序
var sortedDesc = products.OrderByDescending(p => p.Price); // 降冪排序
// 多欄位排序
var multiSort = products
.OrderBy(p => p.Category) // 先依類別排序
.ThenByDescending(p => p.Price); // 同類別再依價格降冪
// GroupBy — 分組
var groups = products.GroupBy(p => p.Category); // 依類別分組
foreach (var group in groups) // 走訪每個分組
{
Console.WriteLine($"== {group.Key} =="); // Key 是分組的依據
foreach (var item in group) // 走訪分組內的每個項目
{
Console.WriteLine($" {item.Name}: {item.Price} 元");
}
}
// 輸出:
// == 水果 ==
// 蘋果: 30 元
// 香蕉: 20 元
// 橘子: 25 元
// == 飲料 ==
// 牛奶: 45 元
// 咖啡: 60 元
// Join — 合併兩個集合
var categories = new[] { ("水果", "🍎"), ("飲料", "🥤") }; // 類別與圖示
var joined = products.Join(
categories, // 要合併的集合
p => p.Category, // 左邊的 key
c => c.Item1, // 右邊的 key
(p, c) => $"{c.Item2} {p.Name} - {p.Price}元" // 合併後的結果
);
// joined = ["🍎 蘋果 - 30元", "🥤 牛奶 - 45元", ...]
📌 First / Any / Count 等查詢方法
int[] numbers = { 5, 3, 8, 1, 9, 2, 7 }; // 數字陣列
// First / FirstOrDefault — 取第一個
int first = numbers.First(); // 取第一個元素 → 5
int firstBig = numbers.First(n => n > 6); // 第一個 > 6 的 → 8
int firstOrDef = numbers.FirstOrDefault(n => n > 100); // 找不到 → 回傳 0
// Single — 取唯一一個(如果有多個會拋出例外)
// int single = numbers.Single(n => n > 8); // 只有 9 符合 → 9
// int singleFail = numbers.Single(n => n > 5); // 多個符合 → 拋出例外!
// Any / All — 是否存在 / 是否全部符合
bool hasNegative = numbers.Any(n => n < 0); // 有負數嗎?→ false
bool allPositive = numbers.All(n => n > 0); // 全部是正數?→ true
// Count — 計數
int total = numbers.Count(); // 總共幾個 → 7
int bigCount = numbers.Count(n => n > 5); // > 5 的有幾個 → 3
📌 聚合方法
int[] scores = { 85, 92, 68, 45, 78 }; // 成績陣列
// 基本聚合
int sum = scores.Sum(); // 總和 → 368
double avg = scores.Average(); // 平均 → 73.6
int max = scores.Max(); // 最大值 → 92
int min = scores.Min(); // 最小值 → 45
// Aggregate — 自定義聚合(進階)
// 把所有名字用逗號串起來
string[] names = { "小明", "小美", "小華" };
string joined = names.Aggregate((a, b) => $"{a}, {b}");
// joined = "小明, 小美, 小華"
// 用 Aggregate 計算階乘
int factorial = Enumerable.Range(1, 5) // 產生 1, 2, 3, 4, 5
.Aggregate((a, b) => a * b); // 1*2*3*4*5 = 120
Console.WriteLine($"5! = {factorial}"); // 輸出:5! = 120
📌 鏈式操作
// LINQ 最強大的地方:把多個操作串在一起
var result = Enumerable.Range(1, 100) // 產生 1 到 100
.Where(n => n % 3 == 0) // 篩選 3 的倍數
.Select(n => new { Value = n, Square = n * n }) // 計算平方
.OrderByDescending(x => x.Square) // 依平方降冪排序
.Take(5) // 只取前 5 個
.ToList(); // 轉成 List
foreach (var item in result) // 走訪結果
{
Console.WriteLine($"{item.Value} 的平方 = {item.Square}");
}
// 輸出(3 的倍數中平方最大的前 5 個):
// 99 的平方 = 9801
// 96 的平方 = 9216
// 93 的平方 = 8649
// 90 的平方 = 8100
// 87 的平方 = 7569
🤔 我這樣寫為什麼會錯?
❌ 錯誤:忘記 LINQ 是延遲執行
var numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1); // 這裡還沒真正執行查詢!
numbers.Add(4); // 加入新元素
// ❌ 以為 query 只有 [2, 3]
// ✅ 實際上 query = [2, 3, 4](因為 LINQ 是在 foreach 時才執行)
foreach (var n in query) Console.Write($"{n} ");
// 輸出:2 3 4
// 解法:如果要立即執行,加上 .ToList()
var list = numbers.Where(n => n > 1).ToList(); // 立即執行並存結果
numbers.Add(5); // 這次不會影響 list
❌ 錯誤:First() 找不到元素
int[] nums = { 1, 2, 3 };
// ❌ 錯誤:First() 找不到符合條件的元素會拋出 InvalidOperationException
// int x = nums.First(n => n > 100); // 拋出例外!
// ✅ 正確:用 FirstOrDefault()
int x = nums.FirstOrDefault(n => n > 100); // 找不到回傳 0