VB.NET List(List(Of T)) 筆記(精進篇)
List(Of T) 是 VB.NET 最常用的泛型集合之一。它可以保存同一種型別的多筆資料,並提供新增、插入、刪除、索引存取、查找、篩選、排序與輸出等操作。
List 的重點不是只把資料「放很多筆」,而是讓資料清單可以隨流程變動。名單、購物車、庫存項目、待辦事項、報表列資料、搜尋結果,都很適合先用 List 建立基本集合模型,再依需求做整理與顯示。
先理解 List 在做什麼
List(Of T):可動態調整大小、可用索引存取、並且限制元素型別的泛型集合。T 代表元素型別,例如 List(Of String) 只能放字串,List(Of ProductItem) 只能放 ProductItem 物件。
List 的核心特性
- 型別安全:集合中的元素型別固定,不容易混入不相關資料。
- 數量可變:可以依流程新增或刪除,不必先固定容量。
- 保留順序:資料加入後會有排列順序,可用索引取得。
- 允許重複:同一個值或同一類物件可以重複出現。
- 方法完整:內建
Add、Insert、Remove、Find、FindAll、Sort等方法。
| 比較項目 | Array | List(Of T) |
|---|---|---|
| 大小 | 建立後固定。 | 可動態增加或刪除元素。 |
| 索引存取 | 支援。 | 支援。 |
| 新增刪除 | 不方便,通常要重建資料。 | 有內建方法可直接操作。 |
| 適合情境 | 筆數固定的資料。 | 筆數會變動的資料清單。 |
基本語法
Dim taskNames As New List(Of String)()
taskNames.Add("整理報表")
taskNames.Add("列印標籤")
taskNames.Insert(0, "登入系統")
taskNames.Remove("列印標籤")
Dim firstTask As String = taskNames(0)
Dim taskCount As Integer = taskNames.Count
新增、插入、刪除與索引存取
場景一:活動報到序號清單
這個範例用活動報到序號示範 List 的基本生命週期。序號可以加入尾端,也可以插入指定位置;刪除時可依序號內容移除;顯示時用索引列出目前順序。
需要的主控項
TextBoxTicketCode:輸入報到序號。NumericUpDownInsertIndex:輸入插入位置。ButtonAddTicket:加入序號。ButtonInsertTicket:插入序號。ButtonRemoveTicket:刪除序號。ListBoxTickets:顯示序號清單。LabelTicketStatus:顯示狀態。
範例程式碼
Public Class Form1
Private ticketCodes As New List(Of String)()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
NumericUpDownInsertIndex.Minimum = 0
NumericUpDownInsertIndex.Maximum = 999
LabelTicketStatus.Text = "尚未加入報到序號"
End Sub
Private Sub ButtonAddTicket_Click(sender As Object, e As EventArgs) Handles ButtonAddTicket.Click
Dim code As String = TextBoxTicketCode.Text.Trim().ToUpper()
If code = String.Empty Then
LabelTicketStatus.Text = "請輸入報到序號"
Return
End If
If ticketCodes.Contains(code) Then
LabelTicketStatus.Text = "序號已存在"
Return
End If
ticketCodes.Add(code)
RefreshTicketList()
LabelTicketStatus.Text = "已加入:" & code
End Sub
Private Sub ButtonInsertTicket_Click(sender As Object, e As EventArgs) Handles ButtonInsertTicket.Click
Dim code As String = TextBoxTicketCode.Text.Trim().ToUpper()
If code = String.Empty Then
LabelTicketStatus.Text = "請輸入報到序號"
Return
End If
If ticketCodes.Contains(code) Then
LabelTicketStatus.Text = "序號已存在"
Return
End If
Dim insertIndex As Integer = CInt(NumericUpDownInsertIndex.Value)
If insertIndex > ticketCodes.Count Then
insertIndex = ticketCodes.Count
End If
ticketCodes.Insert(insertIndex, code)
RefreshTicketList()
LabelTicketStatus.Text = "已插入:" & code & " / 位置:" & insertIndex.ToString()
End Sub
Private Sub ButtonRemoveTicket_Click(sender As Object, e As EventArgs) Handles ButtonRemoveTicket.Click
Dim code As String = TextBoxTicketCode.Text.Trim().ToUpper()
If code = String.Empty Then
LabelTicketStatus.Text = "請輸入要刪除的序號"
Return
End If
If ticketCodes.Remove(code) Then
RefreshTicketList()
LabelTicketStatus.Text = "已刪除:" & code
Else
LabelTicketStatus.Text = "找不到指定序號"
End If
End Sub
Private Sub RefreshTicketList()
ListBoxTickets.Items.Clear()
For index As Integer = 0 To ticketCodes.Count - 1
ListBoxTickets.Items.Add(index.ToString("00") & ":" & ticketCodes(index))
Next
End Sub
End Class
邏輯解析
Add會把資料加入清單尾端。Insert可把資料放到指定索引位置。Remove會依內容移除第一筆符合資料,並回傳是否成功。- 使用索引前要確認範圍,避免超出清單有效位置。
物件集合:讓每筆資料有多個欄位
場景二:課後材料包清單
實務資料通常不只是一個字串。材料包會有名稱、分類、數量與單價,適合用類別表示一筆資料,再用 List(Of MaterialPack) 保存多筆資料。
需要的主控項
TextBoxPackName:輸入材料包名稱。TextBoxPackType:輸入分類。TextBoxPackQty:輸入數量。TextBoxPackPrice:輸入單價。ButtonAddPack:加入材料包。ListBoxPacks:顯示清單。LabelPackStatus:顯示狀態。
範例程式碼
Public Class MaterialPack
Public Property PackName As String
Public Property PackType As String
Public Property Quantity As Integer
Public Property UnitPrice As Decimal
Public Sub New(packName As String, packType As String, quantity As Integer, unitPrice As Decimal)
Me.PackName = packName
Me.PackType = packType
Me.Quantity = quantity
Me.UnitPrice = unitPrice
End Sub
Public ReadOnly Property TotalAmount As Decimal
Get
Return Quantity * UnitPrice
End Get
End Property
Public Function ToDisplayText() As String
Return PackName & " / " & PackType & " / 數量:" & Quantity.ToString() &
" / 小計:" & TotalAmount.ToString("N0")
End Function
End Class
Public Class Form1
Private packs As New List(Of MaterialPack)()
Private Sub ButtonAddPack_Click(sender As Object, e As EventArgs) Handles ButtonAddPack.Click
Dim packName As String = TextBoxPackName.Text.Trim()
Dim packType As String = TextBoxPackType.Text.Trim()
Dim quantity As Integer
Dim unitPrice As Decimal
If packName = String.Empty OrElse packType = String.Empty Then
LabelPackStatus.Text = "名稱與分類不可空白"
Return
End If
If Not Integer.TryParse(TextBoxPackQty.Text.Trim(), quantity) OrElse quantity <= 0 Then
LabelPackStatus.Text = "數量格式錯誤"
Return
End If
If Not Decimal.TryParse(TextBoxPackPrice.Text.Trim(), unitPrice) OrElse unitPrice < 0D Then
LabelPackStatus.Text = "單價格式錯誤"
Return
End If
packs.Add(New MaterialPack(packName, packType, quantity, unitPrice))
RefreshPackList(packs)
LabelPackStatus.Text = "材料包已加入"
End Sub
Private Sub RefreshPackList(source As List(Of MaterialPack))
ListBoxPacks.Items.Clear()
For Each pack As MaterialPack In source
ListBoxPacks.Items.Add(pack.ToDisplayText())
Next
End Sub
End Class
邏輯解析
MaterialPack代表一筆具有多個欄位的資料。List(Of MaterialPack)保存多筆材料包資料。- 顯示文字由物件自己的
ToDisplayText方法產生,表單只負責把結果放到畫面。
查找、篩選與存在性判斷
| 方法 | 用途 | 適合情境 |
|---|---|---|
| Find | 找第一筆符合條件的元素。 | 找第一個符合關鍵字的項目。 |
| FindAll | 找出所有符合條件的元素。 | 篩選分類、庫存、狀態。 |
| Exists | 判斷是否至少有一筆符合。 | 檢查是否有缺貨、重複、異常。 |
| IndexOf | 找出元素位置。 | 簡單值清單的位置查找。 |
場景三:點心庫存搜尋面板
這個範例用點心庫存示範 Find、FindAll 與 Exists。搜尋單筆時只顯示第一筆符合資料;篩選時會列出所有符合庫存門檻的項目。
需要的主控項
TextBoxSnackKeyword:輸入關鍵字。NumericUpDownMinStock:設定最低庫存。ButtonFindSnack:搜尋第一筆。ButtonFilterSnack:依庫存篩選。ButtonCheckEmptySnack:檢查是否有零庫存。ListBoxSnackResult:顯示結果。LabelSnackStatus:顯示狀態。
範例程式碼
Public Class SnackItem
Public Property SnackName As String
Public Property CategoryName As String
Public Property StockCount As Integer
Public Sub New(snackName As String, categoryName As String, stockCount As Integer)
Me.SnackName = snackName
Me.CategoryName = categoryName
Me.StockCount = stockCount
End Sub
Public Function ToDisplayText() As String
Return SnackName & " / " & CategoryName & " / 庫存:" & StockCount.ToString()
End Function
End Class
Public Class Form1
Private snacks As New List(Of SnackItem) From {
New SnackItem("海鹽餅乾", "餅乾", 18),
New SnackItem("蜂蜜蛋糕", "甜點", 5),
New SnackItem("黑糖麻糬", "甜點", 0),
New SnackItem("烘焙堅果", "零食", 12),
New SnackItem("蘋果果乾", "果乾", 7)
}
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
NumericUpDownMinStock.Minimum = 0
NumericUpDownMinStock.Maximum = 999
RefreshSnackList(snacks)
End Sub
Private Sub ButtonFindSnack_Click(sender As Object, e As EventArgs) Handles ButtonFindSnack.Click
Dim keyword As String = TextBoxSnackKeyword.Text.Trim()
If keyword = String.Empty Then
LabelSnackStatus.Text = "請輸入關鍵字"
Return
End If
Dim found As SnackItem = snacks.Find(Function(item)
Return item.SnackName.Contains(keyword) OrElse
item.CategoryName.Contains(keyword)
End Function)
ListBoxSnackResult.Items.Clear()
If found Is Nothing Then
LabelSnackStatus.Text = "找不到符合資料"
Return
End If
ListBoxSnackResult.Items.Add(found.ToDisplayText())
LabelSnackStatus.Text = "已找到第一筆符合資料"
End Sub
Private Sub ButtonFilterSnack_Click(sender As Object, e As EventArgs) Handles ButtonFilterSnack.Click
Dim minStock As Integer = CInt(NumericUpDownMinStock.Value)
Dim result As List(Of SnackItem) = snacks.FindAll(Function(item) item.StockCount >= minStock)
RefreshSnackList(result)
LabelSnackStatus.Text = "符合筆數:" & result.Count.ToString()
End Sub
Private Sub ButtonCheckEmptySnack_Click(sender As Object, e As EventArgs) Handles ButtonCheckEmptySnack.Click
Dim hasEmptyStock As Boolean = snacks.Exists(Function(item) item.StockCount = 0)
If hasEmptyStock Then
LabelSnackStatus.Text = "目前存在零庫存點心"
Else
LabelSnackStatus.Text = "所有點心皆有庫存"
End If
End Sub
Private Sub RefreshSnackList(source As List(Of SnackItem))
ListBoxSnackResult.Items.Clear()
For Each item As SnackItem In source
ListBoxSnackResult.Items.Add(item.ToDisplayText())
Next
End Sub
End Class
邏輯解析
Find只回傳第一筆符合條件的資料。FindAll會建立新的 List,內容是符合條件的元素。Exists只回答是否存在,不需要真的列出資料。- Lambda 條件讓查找規則可以直接寫在方法呼叫中。
排序、反轉與輸出
場景四:課程候補名單排序
List 可以直接排序,也可以先篩選後再排序。這個範例讓候補名單依登記時間、候補序號或姓名排序,並可輸出成一段文字。
需要的主控項
ComboBoxWaitSort:選擇排序方式。ButtonSortWaitList:排序名單。ButtonReverseWaitList:反轉目前順序。ButtonExportWaitList:輸出名單文字。ListBoxWaitList:顯示候補名單。TextBoxWaitExport:顯示輸出文字,建議設定Multiline=True。LabelWaitStatus:顯示狀態。
範例程式碼
Imports System.Linq
Public Class WaitPerson
Public Property QueueNo As Integer
Public Property DisplayName As String
Public Property RegisteredAt As DateTime
Public Sub New(queueNo As Integer, displayName As String, registeredAt As DateTime)
Me.QueueNo = queueNo
Me.DisplayName = displayName
Me.RegisteredAt = registeredAt
End Sub
Public Function ToDisplayText() As String
Return QueueNo.ToString("000") & " / " & DisplayName & " / " &
RegisteredAt.ToString("HH:mm")
End Function
End Class
Public Class Form1
Private waitList As New List(Of WaitPerson) From {
New WaitPerson(12, "林小姐", New DateTime(2025, 4, 20, 9, 35, 0)),
New WaitPerson(3, "陳先生", New DateTime(2025, 4, 20, 9, 10, 0)),
New WaitPerson(8, "王同學", New DateTime(2025, 4, 20, 9, 22, 0))
}
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ComboBoxWaitSort.Items.Clear()
ComboBoxWaitSort.Items.Add("依候補序號")
ComboBoxWaitSort.Items.Add("依登記時間")
ComboBoxWaitSort.Items.Add("依姓名")
ComboBoxWaitSort.SelectedIndex = 0
RefreshWaitList()
End Sub
Private Sub ButtonSortWaitList_Click(sender As Object, e As EventArgs) Handles ButtonSortWaitList.Click
Select Case ComboBoxWaitSort.SelectedIndex
Case 0
waitList.Sort(Function(a, b) a.QueueNo.CompareTo(b.QueueNo))
Case 1
waitList.Sort(Function(a, b) a.RegisteredAt.CompareTo(b.RegisteredAt))
Case 2
waitList.Sort(Function(a, b) String.Compare(a.DisplayName, b.DisplayName))
End Select
RefreshWaitList()
LabelWaitStatus.Text = "排序完成"
End Sub
Private Sub ButtonReverseWaitList_Click(sender As Object, e As EventArgs) Handles ButtonReverseWaitList.Click
waitList.Reverse()
RefreshWaitList()
LabelWaitStatus.Text = "已反轉目前順序"
End Sub
Private Sub ButtonExportWaitList_Click(sender As Object, e As EventArgs) Handles ButtonExportWaitList.Click
Dim lines() As String = waitList.Select(Function(item) item.ToDisplayText()).ToArray()
TextBoxWaitExport.Text = String.Join(Environment.NewLine, lines)
LabelWaitStatus.Text = "名單文字已輸出"
End Sub
Private Sub RefreshWaitList()
ListBoxWaitList.Items.Clear()
For Each item As WaitPerson In waitList
ListBoxWaitList.Items.Add(item.ToDisplayText())
Next
End Sub
End Class
邏輯解析
Sort可搭配比較函式決定排序規則。Reverse是把目前順序反過來,不是重新套用排序條件。Select(...).ToArray()可把物件清單轉成字串陣列。String.Join適合把多筆資料組成可複製或匯出的文字。
參考型別元素的影響
重要觀念:當 List(Of T) 的 T 是 Class 時,List 裡保存的是物件參考。取出某個元素後修改它的屬性,清單中的該物件也會看到變更。
場景五:借物單狀態更新
這個範例從 List 中找出指定借物單,直接修改該物件的狀態。因為清單保存的是同一個物件參考,所以重新整理畫面後狀態會變成「已歸還」。
需要的主控項
TextBoxBorrowNo:輸入借物單號。ButtonReturnItem:更新為已歸還。ListBoxBorrowList:顯示借物單。LabelBorrowStatus:顯示狀態。
範例程式碼
Public Class BorrowRecord
Public Property BorrowNo As String
Public Property ItemName As String
Public Property Returned As Boolean
Public Sub New(borrowNo As String, itemName As String, returned As Boolean)
Me.BorrowNo = borrowNo
Me.ItemName = itemName
Me.Returned = returned
End Sub
Public Function ToDisplayText() As String
Return BorrowNo & " / " & ItemName & " / " & If(Returned, "已歸還", "借出中")
End Function
End Class
Public Class Form1
Private borrowList As New List(Of BorrowRecord) From {
New BorrowRecord("BR-001", "投影筆", False),
New BorrowRecord("BR-002", "延長線", False),
New BorrowRecord("BR-003", "白板筆組", True)
}
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
RefreshBorrowList()
End Sub
Private Sub ButtonReturnItem_Click(sender As Object, e As EventArgs) Handles ButtonReturnItem.Click
Dim targetNo As String = TextBoxBorrowNo.Text.Trim().ToUpper()
If targetNo = String.Empty Then
LabelBorrowStatus.Text = "請輸入借物單號"
Return
End If
Dim record As BorrowRecord = borrowList.Find(Function(item) item.BorrowNo = targetNo)
If record Is Nothing Then
LabelBorrowStatus.Text = "找不到借物單"
Return
End If
record.Returned = True
RefreshBorrowList()
LabelBorrowStatus.Text = "已更新歸還狀態"
End Sub
Private Sub RefreshBorrowList()
ListBoxBorrowList.Items.Clear()
For Each item As BorrowRecord In borrowList
ListBoxBorrowList.Items.Add(item.ToDisplayText())
Next
End Sub
End Class
邏輯解析
BorrowRecord是 Class,所以清單保存的是物件參考。record.Returned = True修改的是清單中的同一個物件。- 若元素型別是 Structure,更新方式通常需要取出、修改、再寫回清單。
刪除資料與列舉限制
不要在 For Each 中直接修改同一個 List 結構
For Each 正在列舉清單時,若同時對同一個 List 呼叫 Add、Remove 或 RemoveAt 改變集合結構,可能拋出 InvalidOperationException。需要刪除多筆資料時,可以使用倒序 For,或使用 RemoveAll。
場景六:移除過期優惠券
這個範例示範兩種安全移除方式。若刪除條件簡單,可使用 RemoveAll;若刪除時還要同時記錄每一筆內容,可使用倒序 For。
需要的主控項
ButtonRemoveExpired:移除過期優惠券。ButtonRemoveExpiredWithLog:倒序移除並記錄。ListBoxCouponList:顯示優惠券。TextBoxRemoveLog:顯示移除紀錄,建議設定Multiline=True。LabelCouponStatus:顯示狀態。
範例程式碼
Public Class CouponInfo
Public Property CouponCode As String
Public Property ExpireDate As DateTime
Public Sub New(couponCode As String, expireDate As DateTime)
Me.CouponCode = couponCode
Me.ExpireDate = expireDate.Date
End Sub
Public Function ToDisplayText() As String
Return CouponCode & " / 到期日:" & ExpireDate.ToString("yyyy/MM/dd")
End Function
End Class
Public Class Form1
Private coupons As New List(Of CouponInfo)()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ResetCoupons()
RefreshCouponList()
End Sub
Private Sub ButtonRemoveExpired_Click(sender As Object, e As EventArgs) Handles ButtonRemoveExpired.Click
Dim today As DateTime = DateTime.Today
Dim removedCount As Integer = coupons.RemoveAll(Function(item) item.ExpireDate < today)
RefreshCouponList()
LabelCouponStatus.Text = "已移除過期優惠券:" & removedCount.ToString() & " 張"
End Sub
Private Sub ButtonRemoveExpiredWithLog_Click(sender As Object, e As EventArgs) Handles ButtonRemoveExpiredWithLog.Click
Dim today As DateTime = DateTime.Today
TextBoxRemoveLog.Clear()
For index As Integer = coupons.Count - 1 To 0 Step -1
If coupons(index).ExpireDate < today Then
TextBoxRemoveLog.Text &= "移除:" & coupons(index).CouponCode & Environment.NewLine
coupons.RemoveAt(index)
End If
Next
RefreshCouponList()
LabelCouponStatus.Text = "倒序移除完成"
End Sub
Private Sub ResetCoupons()
coupons.Clear()
coupons.Add(New CouponInfo("CP-A100", DateTime.Today.AddDays(-2)))
coupons.Add(New CouponInfo("CP-B200", DateTime.Today.AddDays(5)))
coupons.Add(New CouponInfo("CP-C300", DateTime.Today.AddDays(-1)))
coupons.Add(New CouponInfo("CP-D400", DateTime.Today.AddDays(10)))
End Sub
Private Sub RefreshCouponList()
ListBoxCouponList.Items.Clear()
For Each item As CouponInfo In coupons
ListBoxCouponList.Items.Add(item.ToDisplayText())
Next
End Sub
End Class
邏輯解析
RemoveAll適合依條件一次刪除多筆資料。- 倒序
For搭配RemoveAt可避免刪除後索引位移造成漏刪。 For Each適合讀取,不適合在列舉中直接改變同一個 List 的結構。
List 與其他集合的選擇
| 集合型別 | 適合用途 | 判斷重點 |
|---|---|---|
| List(Of T) | 有順序、可重複、可索引的資料清單。 | 最通用的集合起點。 |
| HashSet(Of T) | 不允許重複值。 | 重複檢查比 List 更直接。 |
| Dictionary(Of TKey, TValue) | 用鍵快速找到對應值。 | 適合代碼查資料、編號查物件。 |
| Queue(Of T) | 先進先出流程。 | 適合排隊、任務佇列。 |
| Stack(Of T) | 後進先出流程。 | 適合復原、返回、暫存狀態。 |
選型提醒:List 很通用,但不代表所有集合需求都該使用 List。若重點是「唯一值」,可考慮 HashSet;若重點是「用代碼快速查資料」,可考慮 Dictionary;若重點是「依序排隊處理」,可考慮 Queue。
綜合應用:小型庫存面板
場景七:飲品庫存管理
這個範例整合新增、查找、篩選、排序、統計與畫面重整。List 作為原始資料來源,畫面顯示可以依關鍵字和排序方式產生不同結果,但不破壞原本資料模型。
需要的主控項
TextBoxDrinkName:輸入飲品名稱。TextBoxDrinkType:輸入分類。TextBoxDrinkPrice:輸入單價。TextBoxDrinkStock:輸入庫存。TextBoxDrinkFilter:輸入篩選關鍵字。ComboBoxDrinkSort:選擇排序方式。ButtonAddDrink:新增飲品。ButtonApplyDrinkView:套用篩選與排序。ButtonDrinkSummary:更新摘要。ListBoxDrinkList:顯示飲品清單。LabelDrinkStatus:顯示狀態。LabelDrinkSummary:顯示摘要。
範例程式碼
Imports System.Linq
Public Class DrinkStockItem
Public Property DrinkName As String
Public Property DrinkType As String
Public Property UnitPrice As Decimal
Public Property StockCount As Integer
Public Sub New(drinkName As String, drinkType As String, unitPrice As Decimal, stockCount As Integer)
Me.DrinkName = drinkName
Me.DrinkType = drinkType
Me.UnitPrice = unitPrice
Me.StockCount = stockCount
End Sub
Public ReadOnly Property StockAmount As Decimal
Get
Return UnitPrice * StockCount
End Get
End Property
Public Function ToDisplayText() As String
Return DrinkName & " / " & DrinkType & " / 單價:" & UnitPrice.ToString("N0") &
" / 庫存:" & StockCount.ToString()
End Function
End Class
Public Class Form1
Private drinks As New List(Of DrinkStockItem) From {
New DrinkStockItem("蜜香紅茶", "茶飲", 45D, 18),
New DrinkStockItem("檸檬氣泡水", "氣泡飲", 55D, 9),
New DrinkStockItem("燕麥拿鐵", "咖啡", 75D, 6),
New DrinkStockItem("桂花烏龍", "茶飲", 50D, 0)
}
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ComboBoxDrinkSort.Items.Clear()
ComboBoxDrinkSort.Items.Add("名稱")
ComboBoxDrinkSort.Items.Add("價格由高到低")
ComboBoxDrinkSort.Items.Add("庫存由低到高")
ComboBoxDrinkSort.SelectedIndex = 0
ApplyDrinkView()
UpdateDrinkSummary()
End Sub
Private Sub ButtonAddDrink_Click(sender As Object, e As EventArgs) Handles ButtonAddDrink.Click
Dim drinkName As String = TextBoxDrinkName.Text.Trim()
Dim drinkType As String = TextBoxDrinkType.Text.Trim()
Dim unitPrice As Decimal
Dim stockCount As Integer
If drinkName = String.Empty OrElse drinkType = String.Empty Then
LabelDrinkStatus.Text = "名稱與分類不可空白"
Return
End If
If Not Decimal.TryParse(TextBoxDrinkPrice.Text.Trim(), unitPrice) OrElse unitPrice < 0D Then
LabelDrinkStatus.Text = "單價格式錯誤"
Return
End If
If Not Integer.TryParse(TextBoxDrinkStock.Text.Trim(), stockCount) OrElse stockCount < 0 Then
LabelDrinkStatus.Text = "庫存格式錯誤"
Return
End If
drinks.Add(New DrinkStockItem(drinkName, drinkType, unitPrice, stockCount))
ApplyDrinkView()
UpdateDrinkSummary()
LabelDrinkStatus.Text = "飲品已加入"
End Sub
Private Sub ButtonApplyDrinkView_Click(sender As Object, e As EventArgs) Handles ButtonApplyDrinkView.Click
ApplyDrinkView()
LabelDrinkStatus.Text = "已套用目前條件"
End Sub
Private Sub ButtonDrinkSummary_Click(sender As Object, e As EventArgs) Handles ButtonDrinkSummary.Click
UpdateDrinkSummary()
LabelDrinkStatus.Text = "摘要已更新"
End Sub
Private Sub ApplyDrinkView()
Dim viewList As List(Of DrinkStockItem) = New List(Of DrinkStockItem)(drinks)
Dim keyword As String = TextBoxDrinkFilter.Text.Trim()
If keyword <> String.Empty Then
viewList = viewList.FindAll(Function(item)
Return item.DrinkName.Contains(keyword) OrElse
item.DrinkType.Contains(keyword)
End Function)
End If
Select Case ComboBoxDrinkSort.SelectedIndex
Case 0
viewList.Sort(Function(a, b) String.Compare(a.DrinkName, b.DrinkName))
Case 1
viewList.Sort(Function(a, b) b.UnitPrice.CompareTo(a.UnitPrice))
Case 2
viewList.Sort(Function(a, b) a.StockCount.CompareTo(b.StockCount))
End Select
RefreshDrinkList(viewList)
End Sub
Private Sub RefreshDrinkList(source As List(Of DrinkStockItem))
ListBoxDrinkList.Items.Clear()
For Each item As DrinkStockItem In source
ListBoxDrinkList.Items.Add(item.ToDisplayText())
Next
End Sub
Private Sub UpdateDrinkSummary()
Dim totalKinds As Integer = drinks.Count
Dim totalStock As Integer = drinks.Sum(Function(item) item.StockCount)
Dim totalAmount As Decimal = drinks.Sum(Function(item) item.StockAmount)
Dim zeroStockKinds As Integer = drinks.Count(Function(item) item.StockCount = 0)
LabelDrinkSummary.Text = "品項數:" & totalKinds.ToString() &
" / 總庫存:" & totalStock.ToString() &
" / 庫存總值:" & totalAmount.ToString("N0") &
" / 零庫存:" & zeroStockKinds.ToString()
End Sub
End Class
整合重點
drinks是原始資料來源。New List(Of DrinkStockItem)(drinks)建立檢視用副本,避免排序時直接改變原始順序。FindAll負責篩選,Sort負責排序。Sum與Count可快速完成摘要統計。
實務判斷與常見誤區
常見問題整理
- 把 List 當成所有集合的唯一選擇:需要唯一性或鍵值查找時,可能更適合
HashSet或Dictionary。 - 索引沒有檢查範圍:使用
list(index)前應確認index >= 0且index < list.Count。 - 在 For Each 中增刪同一個 List:容易造成列舉期間集合被修改的錯誤。
- 排序直接改變原清單:
Sort會改變目前 List 的順序,若要保留原順序,先建立副本。 - 忽略 Class 元素的參考行為:清單中的 Class 物件被取出後修改屬性,原清單中的物件也會變動。
- 用 Contains 做複雜物件比對:物件比對需要定義相等規則,或改用
Exists搭配指定條件。
| 需求 | 建議方法 | 原因 |
|---|---|---|
| 加入尾端 | Add |
最常見的新增方式。 |
| 插入指定位置 | Insert |
需要先確認索引範圍。 |
| 刪除符合條件多筆資料 | RemoveAll |
條件清楚時最簡潔。 |
| 找第一筆資料 | Find |
只需要一筆結果。 |
| 篩選多筆資料 | FindAll |
會回傳新的 List。 |
| 排序顯示 | Sort |
會改變目前 List 順序,必要時先複製。 |
重點整理
List(Of T)是可動態調整大小、可索引存取、具型別安全的泛型集合。- List 適合有順序、可重複、筆數會變動的資料清單。
Add加到尾端,Insert插入指定位置,Remove依內容移除。Find找第一筆,FindAll找多筆,Exists判斷是否存在。Sort會改變目前 List 的順序;保留原順序時,應先建立副本。- List 中若保存 Class 物件,取出後修改屬性會影響清單中的同一個物件。
- 列舉同一個 List 時,不應直接增刪該 List 結構;可改用
RemoveAll或倒序For。 - 需要唯一性、鍵值查找或佇列流程時,應評估
HashSet、Dictionary或Queue。