Day 16、Day 17 我們開始學習 Collection。
例如:
List<Order> orders =
new List<Order>
{
new Order("A001", "TXF", 5),
new Order("A002", "MXF", 20),
new Order("A003", "TXF", 15)
};
如果要找出:
Quantity > 10
目前可以:
foreach (Order order in orders)
{
if (order.Quantity > 10)
{
Console.WriteLine(order.OrderId);
}
}
但條件可能一直改變:
Quantity > 10
Product == "TXF"
Quantity == 5
真正改變的其實只有:
判斷條件
因此今天要學:
Lambda Expression
把「會改變的判斷規則」獨立出來。
先看一般 Method:
bool IsLargeOrder(Order order)
{
return order.Quantity > 10;
}
它可以理解成:
收到 Order
↓
判斷 Quantity > 10
↓
回傳 bool
相同的判斷邏輯,也可以使用 Lambda 表示:
order => order.Quantity > 10
同樣代表:
Order
↓
判斷
↓
bool
所以:
一般 Method
→ 有名稱的函式邏輯
Lambda
→ 通常是匿名的函式邏輯
Lambda 常見的基本形式:
Parameter
↓
=>
↓
Expression
例如:
order => order.Quantity > 10
拆開:
order
→ Parameter
=>
→ Lambda Operator
order.Quantity > 10
→ 要執行的判斷
可以讀成:
給我一個
order,判斷它的Quantity是否大於 10。
流程:
收到 Order
↓
Quantity > 10
↓
true / false
比較:
bool IsLargeOrder(Order order)
{
return order.Quantity > 10;
}
order => order.Quantity > 10
兩者可以表達相同的判斷,但使用方式不同。
| Method | Lambda |
|---|---|
| 有明確名稱 | 通常匿名 |
| 適合重複使用 | 適合局部使用 |
| 適合較複雜邏輯 | 適合短小規則 |
| 名稱可以表達業務意義 | 可以直接看到當下條件 |
可以先這樣判斷:
短小、只使用一次
→ Lambda
重要、複雜、會重複使用
→ Method
但要注意:
Lambda 和 Method 都可以提供函式邏輯,不是互相排斥的兩種東西。
Func<Order, bool> 是什麼?接下來會看到:
Func<Order, bool>
Func<...> 是 .NET 提供的一種 Delegate Type。
這篇不深入 Delegate 原理,可以先理解成:
Func可以參考一段「有回傳值的函式邏輯」。
例如:
Func<Order, bool>
↑ ↑
輸入 回傳
代表:
收到 Order
↓
執行某個判斷
↓
回傳 bool
所以:
order => order.Quantity > 10
符合:
Func<Order, bool>
因為它也是:
Order
↓
bool
例如:
Func<Order, bool> condition =
order => order.Quantity > 10;
這時只是建立一段判斷邏輯。
並沒有立即判斷任何 Order。
真正執行是在:
condition(order);
假設:
order.Quantity = 20
流程:
condition(order)
↓
把 order 傳進 Lambda
↓
20 > 10
↓
true
所以:
建立 Lambda
→ 表示一段函式邏輯
呼叫 Lambda
→ 真正執行函式邏輯
Func 可以有多個 ParameterFunc 不一定只有一個輸入。
例如:
Func<int, int, int> add =
(x, y) => x + y;
這裡:
Func<int, int, int>
↑ ↑ ↑
輸入 輸入 回傳
代表:
收到 int
收到 int
↓
回傳 int
所以:
(x, y) => x + y
可以讀成:
給我
x和y,回傳兩者相加的結果。
例如:
int result =
add(3, 5);
Console.WriteLine(result);
結果:
8
初學可以先記:
Func最後一個 Type 是回傳型別,前面的 Type 都是輸入。
例如:
Func<int, bool>
int
↓
bool
以及:
Func<int, int, int>
int
int
↓
int
Action如果一段函式邏輯:
只需要執行動作
不需要回傳結果
可以使用:
Action<string> sayHello =
name =>
Console.WriteLine($"Hello, {name}");
使用:
sayHello("Amy");
結果:
Hello, Amy
這裡:
Action<string>
可以理解成:
收到 string
↓
執行動作
↓
沒有回傳值
所以初學可以這樣區分:
| Type | 用途 |
|---|---|
Func |
有回傳值 |
Action |
沒有回傳值 |
例如:
Func<Order, bool>
→ Order → bool
而:
Action<string>
→ 收到 string
→ 執行動作
→ 不回傳資料
這篇後面的主線仍然以:
Func<Order, bool>
為主。
目前看到的:
order => order.Quantity > 10
右邊只有一個 Expression。
這種寫法叫:
Expression Lambda
例如:
Func<int, int> doubleNumber =
number => number * 2;
如果 Lambda 需要執行多個步驟,可以使用 { }:
Func<int, int> doubleNumber =
number =>
{
int result =
number * 2;
Console.WriteLine(result);
return result;
};
這種寫法叫:
Statement Lambda
整理:
Expression Lambda
→ 一個 Expression
→ 寫法簡潔
Statement Lambda
→ 使用 { }
→ 可以有多個 Statement
→ 有回傳值時要使用 return
例如:
number => number * 2
適合簡單邏輯。
如果邏輯開始需要:
建立變數
判斷
輸出
多個步驟
就可以使用 Statement Lambda。
但如果邏輯越來越複雜,也應該考慮改成有名稱的 Method。
現在回到最一開始的問題。
原本:
foreach (Order order in orders)
{
if (order.Quantity > 10)
{
// 處理
}
}
真正會改變的是:
order.Quantity > 10
因此我們建立:
List<Order> FindOrders(
List<Order> orders,
Func<Order, bool> condition)
{
List<Order> result =
new List<Order>();
foreach (Order order in orders)
{
if (condition(order))
{
result.Add(order);
}
}
return result;
}
這個 Method 有兩個重要 Parameter。
List<Order> orders
代表:
要處理的資料
而:
Func<Order, bool> condition
代表:
要使用的判斷規則
所以:
FindOrders()
資料
+
判斷規則
condition(order) 到底是什麼?這是今天最重要的一行:
if (condition(order))
假設呼叫端傳入:
order => order.Quantity > 10
那麼 condition 參考的就是這段函式邏輯。
因此:
condition(order)
可以理解成:
把目前這筆
order傳進判斷規則,並取得true或false。
例如:
A001
Quantity = 5
執行:
condition(A001)
↓
5 > 10
↓
false
所以不加入 result。
而:
A002
Quantity = 20
執行:
condition(A002)
↓
20 > 10
↓
true
所以加入 result。
核心流程:
foreach 取得 Order
↓
condition(order)
↓
執行判斷規則
↓
true
→ 加入 result
false
→ 不加入
找:
Quantity > 10
List<Order> result =
FindOrders(
orders,
order =>
order.Quantity > 10
);
如果改成:
Product == "TXF"
只需要換掉 Lambda:
List<Order> result =
FindOrders(
orders,
order =>
order.Product == "TXF"
);
如果再改成:
Quantity == 5
List<Order> result =
FindOrders(
orders,
order =>
order.Quantity == 5
);
可以看到:
FindOrders()
→ 固定流程不變
Lambda
→ 判斷規則改變
這就是:
把固定流程與可變規則分開。
假設:
bool IsLargeOrder(Order order)
{
return order.Quantity > 10;
}
它也是:
Order
↓
bool
符合:
Func<Order, bool>
所以也可以:
List<Order> result =
FindOrders(
orders,
IsLargeOrder
);
因此以下兩種都可以。
使用 Lambda:
FindOrders(
orders,
order =>
order.Quantity > 10
);
使用 Method:
FindOrders(
orders,
IsLargeOrder
);
可以這樣理解:
函式邏輯
│
┌───────┴───────┐
↓ ↓
Lambda Method
│ │
└───────┬───────┘
↓
都可以符合
Func<Order, bool>
如果條件:
很短
只使用一次
直接寫在呼叫位置很好懂
適合:
FindOrders(
orders,
order =>
order.Quantity > 10
);
如果規則:
會重複使用
具有明確業務意義
邏輯比較複雜
適合建立 Method:
bool IsLargeOrder(Order order)
{
return order.Quantity > 10;
}
再:
FindOrders(
orders,
IsLargeOrder
);
所以:
短、小、局部
→ Lambda
重要、複雜、重複使用
→ Method
List<Order> orders =
new List<Order>
{
new Order(
"A001",
"TXF",
5
),
new Order(
"A002",
"MXF",
20
),
new Order(
"A003",
"TXF",
15
)
};
List<Order> largeOrders =
FindOrders(
orders,
order =>
order.Quantity > 10
);
foreach (Order order in largeOrders)
{
Console.WriteLine(
order.OrderId
);
}
List<Order> FindOrders(
List<Order> source,
Func<Order, bool> condition)
{
List<Order> result =
new List<Order>();
foreach (Order order in source)
{
if (condition(order))
{
result.Add(order);
}
}
return result;
}
class Order
{
public string OrderId
{
get;
}
public string Product
{
get;
}
public int Quantity
{
get;
}
public Order(
string orderId,
string product,
int quantity)
{
OrderId = orderId;
Product = product;
Quantity = quantity;
}
}
結果:
A002
A003
整體只需要掌握:
List<Order>
↓
FindOrders()
↓
接收判斷規則
↓
condition(order)
↓
執行規則
↓
符合條件
→ 加入 result
今天最重要的是:
Method
→ 有名稱的函式邏輯
Lambda
→ 通常是匿名函式邏輯
而:
Func<Order, bool>
代表:
需要一段:
Order
↓
bool
的函式邏輯
所以可以提供:
Lambda
或
Method
而:
condition(order)
代表:
把 order 傳進函式邏輯
↓
真正執行
↓
取得結果
另外今天也認識:
Func<T1, T2, TResult>
→ 可以有多個輸入
Action<T>
→ 執行動作,不回傳結果
Expression Lambda
→ 單一 Expression
Statement Lambda
→ 使用 { } 撰寫多個 Statement
一句話記住:
Lambda 適合直接表示與傳遞短小的函式規則;有名稱的 Method 更適合重要、複雜或會重複使用的邏輯。
今天我們自己建立:
FindOrders(
orders,
order =>
order.Quantity > 10
);
內部需要:
foreach
↓
condition(order)
↓
加入符合條件的資料
下一篇會看到 LINQ 已經提供:
orders.Where(
order =>
order.Quantity > 10
);
因此 Day 19 會開始學:
Where()
→ 篩選
Select()
→ 轉換
FirstOrDefault()
→ 找第一筆
Any()
→ 判斷是否存在
從:
自己寫 FindOrders()
走向:
使用 LINQ 查詢 Collection