CodeGym /課程 /C# SELF /用 Where 過濾資料

用 Where 過濾資料

C# SELF
等級 31, 課堂 2
開放

1. 前言

過濾就是從集合裡挑出符合某個條件的元素。想像一下:你的資料庫裡有 10 000 個商品,結果經理只要那「區區」12 個,還要符合某個新奇的條件。每次都手動寫 foreach 跟一堆 if,超麻煩,程式碼也會變得很難讀。LINQ 幫你搞定這些雜事。

在實際應用裡,過濾是最常見的資料操作:只顯示使用者感興趣的紀錄、把不想要的元素從集合裡「清掉」、做報表、搜尋或寄 e-mail 的時候都會用到。面試時 LINQ 過濾的題目也超常見。搞懂這一塊,對新手 .NET 工程師來說超重要。

2. 認識 Where 方法

意義跟運作原理

Where 方法是 LINQ 的擴充方法,它會接收一個 function(或 lambda 表達式),這個 function 決定過濾的條件。它會回傳一個新的序列(不會改到原本的!),裡面只會有條件回傳 true 的元素。

主要簽名:

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate)

不用怕 generics 跟那些看起來很兇的詞。現在你只要抓住重點:Where 會吃進你給的集合跟條件。實際上超簡單:

  • source — 你要過濾的集合(像是 List<Product>)
  • predicate — function/條件(像 p => p.Price > 100)

簡單小題目熱身一下

假設我們有一個商品清單,只想挑出價格大於 100 的。以前都要寫 foreach 迴圈,手動把符合的加進新 list。LINQ 讓你一行就搞定。

3. 過濾

我們的 Product 類別跟起始集合

上一個單元我們大概寫過這樣的商品類別:


class Product
{
    public string Name { get; set; }
    public double Price { get; set; }

    // 為了讓輸出好看一點:
    public override string ToString()
    {
        return $"{Name} (價格: {Price})";
    }
}

假設我們有一個商品清單:


var products = new List<Product>
{
    new Product { Name = "麵包", Price = 30 },
    new Product { Name = "牛奶", Price = 87 },
    new Product { Name = "起司", Price = 250 },
    new Product { Name = "巧克力", Price = 130 }
};

用 Where 過濾價格超過 100 的商品


var expensiveProducts = products.Where(p => p.Price > 100);

foreach (var product in expensiveProducts)
{
    Console.WriteLine(product);
}

底層發生什麼事:

  • Where 會把 products 裡的每個元素都跑一次,
  • 每個都丟進 function(這裡是 p => p.Price > 100),
  • 如果 function 回傳 true,這個商品就會進到新集合。

螢幕上的結果:

起司 (價格: 250)
巧克力 (價格: 130)

注意:原本的 products 清單完全沒被動到!LINQ 不會亂動你的資料。

4. 延遲過濾(Deferred Execution)

這裡有個很酷也很重要的點:Where 的結果只會在你真的去用它的時候才計算。像是你開始跑 foreach 迴圈時,LINQ 才會「走」原本的集合,邊走邊過濾。如果你根本沒用結果,啥事都不會發生。這就叫 延遲執行(deferred execution)。

這樣有一堆好處:

  • 你可以串超長的方法鏈(像是又過濾又排序),只有第一次真的用到結果時才會執行。
  • 省記憶體:不會產生一堆中間集合。
  • 需要的時候可以中斷執行,或找到夠多元素就停。

視覺化流程圖


原始集合       —►   Where (條件)    —►   只走需要的元素
    
[ x ] [ x ] [ + ] [ + ]  —►   p.Price > 100      —►   [ + ] [ + ]

(x — 不符合條件的元素,+ — 符合的)

5. 語法變化

經典擴充方法語法(Method Syntax)

我們現在用的就是這種寫法:

var result = products.Where(p => p.Price > 100);

另一種:Query Syntax

LINQ 也支援很像 SQL 查詢的語法:


var result = from p in products
             where p.Price > 100
             select p;

結果一模一樣!
選哪種語法看你爽,不過 method syntax(點方法)在專案裡比較常見,尤其是要串很多方法的時候。

6. 複雜條件過濾

多個條件

你可以用邏輯運算子(&&、||、!):


// 找所有貴的商品,除了「巧克力」
var filtered = products.Where(
    p => p.Price > 100 && p.Name != "巧克力"
);

字串過濾(子字串、大小寫、Contains)


// 名稱裡有「ㄌ」的商品
var lProducts = products.Where(p => p.Name.Contains("ㄌ"));

注意:Contains 會區分大小寫!如果你想「不分大小寫」,可以這樣寫:


var lProducts = products.Where(p => p.Name
    .ToLower().Contains("ㄌ")); // 這樣「牛奶」跟「巧克力」都會被選到

多集合過濾

假設你有兩個 list — 商品跟付款。你可以過濾那些有出現在付款清單裡的商品——不過這時候用 Any 跟 Join 會比較好,這部分之後會講。先記得:LINQ 也能搞很複雜的過濾!

7. 實用小細節

巢狀過濾跟方法鏈

你可以連續呼叫好幾次 Where,把過濾分好幾步:


var filtered = products
    .Where(p => p.Price > 100)
    .Where(p => p.Name.StartsWith("巧"));

不過最好還是把條件寫在同一個 Where,用邏輯運算子連起來——這樣效率比較好,程式碼也比較乾淨。

內建比較器跟自訂 function

有時候內建的運算子不夠用。像是你想過濾字串時不分大小寫又要考慮文化設定。這時候可以用比較方法:


var rusProducts = products.Where(
    p => p.Name.StartsWith("巧", StringComparison.OrdinalIgnoreCase)
);

8. 過濾跟使用者輸入

來試試簡單的互動過濾吧!比如問使用者最小價格,然後顯示所有符合的商品。


Console.Write("商品的最低價格? ");
var minPriceStr = Console.ReadLine();
if (double.TryParse(minPriceStr, out double minPrice))
{
    var filtered = products.Where(p => p.Price >= minPrice);
    foreach (var product in filtered)
    {
        Console.WriteLine(product);
    }
}
else
{
    Console.WriteLine("錯誤:輸入的不是數字。");
}

現在我們這個「迷你小程式」已經有點像網路商店了!

9. 用 Where 常見的錯誤跟陷阱

寫程式跟人生一樣,總有一些坑。這些要小心。

忘記呼叫 ToList() 或 ToArray()

Where 的結果 不是 list 也不是 array!它是一個 IEnumerable<Product> 物件。大部分時候你可以直接在 foreach 裡用,但如果你真的需要集合(比如要用 index 存取),記得要呼叫 .ToList() 或 .ToArray():


var filteredList = products.Where(p => p.Price > 100).ToList();

如果忽略這點,很容易遇到像 "在列舉時集合被修改" 這種錯誤——尤其是你在過濾時又去改原本的集合。

改動原始集合

因為 LINQ 過濾是延遲執行,如果你在跑迴圈時刪掉原本 list 裡的元素,可能會噴例外。這在多執行緒或你在過濾時直接改集合時特別容易發生。

Null 值跟條件 function

如果集合裡有 null,你在條件 function 裡沒檢查就直接用物件的欄位,會噴 NullReferenceException。像這樣:


var filtered = products.Where(p => p.Name.StartsWith("阿"));

如果 products 裡有某個元素是 null,程式就會爆掉。

建議:如果有可能有 null,條件 function 裡一定要先檢查:


var filtered = products.Where(p => p != null && p.Name.StartsWith("阿"));
2
任務
C# SELF, 等級 31, 課堂 2
上鎖
依多個條件過濾物件
依多個條件過濾物件
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION