理解 C# 委託
C#中的委派是一個強大的功能,但許多開發者不熟悉其有效的使用方式。 Tim Corey 提供了一個詳盡的說明,在他針對"C# 中的委派 - 實際演示,包括 Action 和 Func"的視頻中,說明了什麼是委派,如何使用它們,以及它們的用途。
本文將為您介紹 Tim 的專業見解,提供對 C# 中委派使用方式和實際應用的清晰解釋。 您將學習如何通過例子(如在購物車系統中的使用)來提升代碼的靈活性和效率。
介紹
Tim 介紹了委派的概念,強調其在 C# 中的強大和多功能性。 他向觀眾保證,儘管有些術語可能讓人卻步,但委派的基礎是簡單的。 Tim 旨在揭開委派的神秘面紗,並涵蓋像 func 和 action 這樣的特殊類型。
演示應用程序走過
Tim 設置了一個示範應用程式來說明委派的使用。 該解決方案包含三個專案:一個控制台 UI、一個演示程式庫和一個 WinForm UI。 起初的重點是在控制台 UI 和演示程式庫上。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleUI
{
class Program
{
static ShoppingCartModel cart = new ShoppingCartModel();
static void Main(string[] args)
{
PopulateCartWithDemoData();
Console.WriteLine($"The total for the cart is {cart.GenerateTotal():C2}");
Console.ReadLine();
}
private static void PopulateCartWithDemoData()
{
cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M });
cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M });
cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M });
cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M });
}
}
}
public class ShoppingCartModel
{
public List<ProductModel> Items { get; set; } = new List<ProductModel>();
public decimal GenerateTotal()
{
decimal subtotal = Items.Sum(x => x.Price);
if (subtotal > 100)
{
return subtotal * 0.80M;
}
else if (subtotal > 50)
{
return subtotal * 0.85M;
}
else if (subtotal > 10)
{
return subtotal * 0.90M;
}
else
{
return subtotal;
}
}
}
public class ProductModel
{
public string ItemName { get; set; }
public decimal Price { get; set; }
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleUI
{
class Program
{
static ShoppingCartModel cart = new ShoppingCartModel();
static void Main(string[] args)
{
PopulateCartWithDemoData();
Console.WriteLine($"The total for the cart is {cart.GenerateTotal():C2}");
Console.ReadLine();
}
private static void PopulateCartWithDemoData()
{
cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M });
cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M });
cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M });
cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M });
}
}
}
public class ShoppingCartModel
{
public List<ProductModel> Items { get; set; } = new List<ProductModel>();
public decimal GenerateTotal()
{
decimal subtotal = Items.Sum(x => x.Price);
if (subtotal > 100)
{
return subtotal * 0.80M;
}
else if (subtotal > 50)
{
return subtotal * 0.85M;
}
else if (subtotal > 10)
{
return subtotal * 0.90M;
}
else
{
return subtotal;
}
}
}
public class ProductModel
{
public string ItemName { get; set; }
public decimal Price { get; set; }
}
Tim 解釋了示範應用程式的結構和功能:
-
購物車模型:代表購物車,包含一系列商品(ProductModel),並根據小計計算總成本和折扣。
-
產品模型:代表具有名稱和價格屬性的單個商品。
- 控制台應用程式:用演示資料填充購物車,計算總額並顯示。
了解折扣
Tim 逐步講解了 GenerateTotal 方法中的折扣邏輯,解釋小計如何決定應用的折扣:
- 小計超過 $100 享有 20% 的折扣。
- 小計超過 $50 享有 15% 的折扣。
- 小計超過 $10 享有 10% 的折扣。
- 小計 $10 或以下不提供折扣。
Tim 使用斷點展示計算和折扣邏輯,以確保觀眾在引入委派之前了解基礎知識。
解釋和創建一個委派
在這部分,Tim Corey 深入介紹 C# 中委派的概念,解釋其工作原理並通過實際代碼示例演示其使用。
什麼是委派?
Tim 解釋說,委派本質上是一種將方法作為參數傳遞的方法。 與其傳遞變數或屬性,不如傳遞方法,使代碼更具靈活性和重用性。
創建和使用委派
以下是 Tim 如何分解創建和使用委派過程的方式:
-
定義委派:
- 委派在類的頂部定義,指定返回類型和參數類型。
public delegate void MentionDiscount(decimal subtotal);public delegate void MentionDiscount(decimal subtotal);- 該委派指定一個返回 void 並以 decimal 作為參數的方法。
-
在方法中使用委派:
- 委派用作 ShoppingCartModel 類中 GenerateTotal 方法的參數。
public decimal GenerateTotal(MentionDiscount mentionDiscount) { decimal subtotal = Items.Sum(x => x.Price); // Call a method passed as a delegate mentionDiscount(subtotal); if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } }public decimal GenerateTotal(MentionDiscount mentionDiscount) { decimal subtotal = Items.Sum(x => x.Price); // Call a method passed as a delegate mentionDiscount(subtotal); if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } } -
創建一個方法傳遞給委派:
- 在 Program 類中創建一個與委派簽名匹配的方法。
private static void SubtotalAlert(decimal subtotal) { Console.WriteLine($"The subtotal is {subtotal:C2}"); }private static void SubtotalAlert(decimal subtotal) { Console.WriteLine($"The subtotal is {subtotal:C2}"); } -
調用 GenerateTotal 方法:
- 通過委派將方法傳遞給 GenerateTotal 方法。
class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal(SubtotalAlert):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } }class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal(SubtotalAlert):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } }
運行應用程式
Tim 運行應用程式,以演示委派如何工作。 控制台輸出顯示了購物車的小計和總額,表明 SubtotalAlert 方法已成功通過委派傳遞並執行。

Func 和 Action:您可以用委派解決的問題
然後,Tim Corey 探討了在 C# 中使用 func 和 action 委派。 這些是微軟提供的特殊類型的委派,用於簡化泛型委派的使用。
識別問題
Tim 強調了一個常見問題:GenerateTotal 方法中硬編碼的折扣邏輯。 這種方法缺乏靈活性,當折扣規則改變時,需要修改代碼、重新編譯和重新部署。
public decimal GenerateTotal()
{
decimal subtotal = Items.Sum(x => x.Price);
if (subtotal > 100)
{
return subtotal * 0.80M;
}
else if (subtotal > 50)
{
return subtotal * 0.85M;
}
else if (subtotal > 10)
{
return subtotal * 0.90M;
}
else
{
return subtotal;
}
}
public decimal GenerateTotal()
{
decimal subtotal = Items.Sum(x => x.Price);
if (subtotal > 100)
{
return subtotal * 0.80M;
}
else if (subtotal > 50)
{
return subtotal * 0.85M;
}
else if (subtotal > 10)
{
return subtotal * 0.90M;
}
else
{
return subtotal;
}
}
介紹 Func 委派
Tim 介紹了 func 委派,以解決硬編碼的折扣問題。 func 委派是一種泛型委派,表示具有返回類型和最多 16 個輸入參數的方法簽名。
-
定義 Func 委派:
- 在 GenerateTotal 方法中使用 func 委派來動態處理折扣計算。
public decimal GenerateTotal(Func<List<ProductModel>, decimal, decimal> calculateDiscountedTotal) { decimal subtotal = Items.Sum(x => x.Price); MentionDiscount(subtotal); return calculateDiscountedTotal(Items, subtotal); }public decimal GenerateTotal(Func<List<ProductModel>, decimal, decimal> calculateDiscountedTotal) { decimal subtotal = Items.Sum(x => x.Price); MentionDiscount(subtotal); return calculateDiscountedTotal(Items, subtotal); } -
創建折扣計算方法:
- 在 Program 類中創建一個與 func 委派簽名匹配的方法。
private static decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } }private static decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } } -
將方法傳遞給 Func 委派:
- 將 CalculateLevelDiscount 方法通過 func 委派傳遞給 GenerateTotal 方法。
class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal(CalculateLevelDiscount):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } private static decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } } }class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal(CalculateLevelDiscount):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } private static decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } } }
運行應用程式
Tim 演示了修改後的應用程式,顯示它能正常運作並根據提供的邏輯動態計算折扣。

委派和 Func 之間的區別
Tim 比較了自定義委派和 func 委派:
-
委派:需要明確定義簽名,提供清晰的文檔和結構。
- Func:更簡潔,但每次需要指定輸入和輸出類型,這可能不太清楚。
兩種方法都提供了靈活性,但選擇哪種方法取決於應用程式的具體情況和複雜性。
為什麼要使用委派,如果所有工作都在別處完成?
Tim Corey 關注一個常見問題,即關於使用委派:如果所有工作看起來都在別處完成,為什麼需要委派?
Tim 解釋說,委派的目的是為代碼提供靈活性和擴展性。 ShoppingCartModel 類中的 GenerateTotal 方法可能不僅僅是計算折扣。 它可能還會處理檢查庫存情況、驗證購物車內容或其他業務邏輯等任務。 委派允許您傳遞特定方法以進行獨特的任務或自定義行為,而不改變核心方法。 這使代碼更加模塊化和易於維護。
委派尤其適用于以下情況:
- 動態應用不同的業務規則或邏輯。
- 保持核心方法的可重用性和通用性。
- 在不修改核心方法的情況下為特定案例實現自定義行為。
Action 委派:創建和解釋
Tim 介紹了 Action 委派,這是 C# 中的另一種特殊類型的委派。 Action 委派類似於 Func,但它不返回任何值(即返回 void)。
-
創建 Action 委派:
- 在 GenerateTotal 方法中定義 Action 委派,以處理警報或訊息。
public decimal GenerateTotal(Func<List<ProductModel>, decimal, decimal> calculateDiscountedTotal, Action<string> tellUserWeAreDiscounting) { decimal subtotal = Items.Sum(x => x.Price); MentionSubtotal(subtotal); tellUserWeAreDiscounting("We are applying your discount."); return calculateDiscountedTotal(Items, subtotal); }public decimal GenerateTotal(Func<List<ProductModel>, decimal, decimal> calculateDiscountedTotal, Action<string> tellUserWeAreDiscounting) { decimal subtotal = Items.Sum(x => x.Price); MentionSubtotal(subtotal); tellUserWeAreDiscounting("We are applying your discount."); return calculateDiscountedTotal(Items, subtotal); } -
創建警報方法:
- 定義一個與 Action 委派簽名匹配的方法。
private static void AlertUser(string message) { Console.WriteLine(message); }private static void AlertUser(string message) { Console.WriteLine(message); } -
將方法傳遞給 Action 委派:
- 通過 Action 委派將 AlertUser 方法傳遞給 GenerateTotal 方法。
class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal(CalculateLevelDiscount, AlertUser):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } private static decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } } private static void AlertUser(string message) { Console.WriteLine(message); } }class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal(CalculateLevelDiscount, AlertUser):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } private static decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (subtotal > 100) { return subtotal * 0.80M; } else if (subtotal > 50) { return subtotal * 0.85M; } else if (subtotal > 10) { return subtotal * 0.90M; } else { return subtotal; } } private static void AlertUser(string message) { Console.WriteLine(message); } }
創建匿名方法:匿名委派
Tim 展示了如何使用匿名方法,這樣您可以按需定義方法而不必命名它們。
-
定義匿名方法:
- 不用創建命名方法,您可以直接在需要的地方定義方法。
class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal((items, subtotal) => { if (subtotal > 100) return subtotal * 0.80M; else if (subtotal > 50) return subtotal * 0.85M; else if (subtotal > 10) return subtotal * 0.90M; else return subtotal; }, (message) => Console.WriteLine(message)):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } }class Program { static ShoppingCartModel cart = new ShoppingCartModel(); static void Main(string[] args) { PopulateCartWithDemoData(); Console.WriteLine($"The total for the cart is {cart.GenerateTotal((items, subtotal) => { if (subtotal > 100) return subtotal * 0.80M; else if (subtotal > 50) return subtotal * 0.85M; else if (subtotal > 10) return subtotal * 0.90M; else return subtotal; }, (message) => Console.WriteLine(message)):C2}"); Console.ReadLine(); } private static void PopulateCartWithDemoData() { cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M }); cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M }); cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M }); cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M }); } } -
理解語法:
- 匿名方法語法使用
=>運算符(lambda 表達式)直接內聯定義方法體。 - 不需要指定返回類型或方法名稱。
- 匿名方法語法使用
通過使用委派,包括 Func、Action 和匿名方法,開發者可以創建更動態和模塊化的代碼,使其元件靈活且可重用。
在其他專案中使用委派:WinForms
在此部分中,Tim Corey 演示了委派的強大功能,將其使用擴展到 WinForms 應用程式。 這強調了委派如何在不同的用戶介面(UI)上下文中促進不同的行為。
設置 WinForms 應用程式
-
具有兩個按鈕的 WinForm UI:
-
該表單有兩個按鈕:一個用於演示訊息框,另一個用於演示文本框。
- 還包括 ShoppingCartModel 和用於填充演示資料的方法。
-
public partial class Dashboard : Form
{
ShoppingCartModel cart = new ShoppingCartModel();
public Dashboard()
{
InitializeComponent();
PopulateCartWithDemoData();
}
private void PopulateCartWithDemoData()
{
cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M });
cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M });
cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M });
cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M });
}
private void messageBoxDemoButton_Click(object sender, EventArgs e)
{
decimal total = cart.GenerateTotal(SubtotalAlert, CalculateLevelDiscount, PrintOutDiscountAlert);
MessageBox.Show($"The total is {total:C2}");
}
private void textBoxDemoButton_Click(object sender, EventArgs e)
{
// Code for TextBox demo will go here
}
}
public partial class Dashboard : Form
{
ShoppingCartModel cart = new ShoppingCartModel();
public Dashboard()
{
InitializeComponent();
PopulateCartWithDemoData();
}
private void PopulateCartWithDemoData()
{
cart.Items.Add(new ProductModel { ItemName = "Cereal", Price = 3.63M });
cart.Items.Add(new ProductModel { ItemName = "Milk", Price = 2.95M });
cart.Items.Add(new ProductModel { ItemName = "Strawberries", Price = 7.51M });
cart.Items.Add(new ProductModel { ItemName = "Blueberries", Price = 6.75M });
}
private void messageBoxDemoButton_Click(object sender, EventArgs e)
{
decimal total = cart.GenerateTotal(SubtotalAlert, CalculateLevelDiscount, PrintOutDiscountAlert);
MessageBox.Show($"The total is {total:C2}");
}
private void textBoxDemoButton_Click(object sender, EventArgs e)
{
// Code for TextBox demo will go here
}
}
創建委派的方法
-
PrintOutDiscountAlert:
- 此方法將用於顯示包含折扣資訊的警報。
private void PrintOutDiscountAlert(string message) { MessageBox.Show(message); }private void PrintOutDiscountAlert(string message) { MessageBox.Show(message); } -
SubtotalAlert:
- 此方法將在訊息框中顯示小計。
private void SubtotalAlert(decimal subtotal) { MessageBox.Show($"The subtotal is {subtotal:C2}"); }private void SubtotalAlert(decimal subtotal) { MessageBox.Show($"The subtotal is {subtotal:C2}"); } -
CalculateLevelDiscount:
- 此方法將根據購物車中的項目數量計算折扣。
private decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (items.Count > 3) { return subtotal - 3M; } return subtotal - items.Count; }private decimal CalculateLevelDiscount(List<ProductModel> items, decimal subtotal) { if (items.Count > 3) { return subtotal - 3M; } return subtotal - items.Count; }
將委派整合到 WinForms
-
在按鈕點擊事件中使用委派:
messageBoxDemoButton_Click方法展示如何將委派傳遞給 GenerateTotal 方法,並使用訊息框處理結果。
private void messageBoxDemoButton_Click(object sender, EventArgs e) { decimal total = cart.GenerateTotal(SubtotalAlert, CalculateLevelDiscount, PrintOutDiscountAlert); MessageBox.Show($"The total is {total:C2}"); }private void messageBoxDemoButton_Click(object sender, EventArgs e) { decimal total = cart.GenerateTotal(SubtotalAlert, CalculateLevelDiscount, PrintOutDiscountAlert); MessageBox.Show($"The total is {total:C2}"); } -
運行應用程式:
- 當按鈕被點擊時,WinForms 應用程式通過訊息框展示小計和總額,演示了委派的靈活性。

結論
Tim Corey 清楚地解釋了 C# 中的委派,涵蓋了其基礎、高級用法和實際例子,如在購物車中使用委派。 他展示了如何讓委派包括 Func、Action 和匿名方法來啟用靈活和可重用的代碼。 觀看完整視頻,學習如何在您的專案中有效應用委派!
