理解C#中的委派
C#中的委派是一個強大的功能,但許多開發者不熟悉如何有效使用。 Tim Corey 在 "Delegates in C# - A practical demonstration, including Action and Func" 的影片中,提供了委派是什麼、如何使用它們以及它們為什麼有用的詳細說明。
這篇文章將提供 Tim 關於 C#中的委派的專業見解,為您清楚說明其使用及實際應用。 您將了解委派如何提升您的程式程式碼的靈活性和效率,並通過如在購物車系統中的使用範例。
介紹
Tim 介紹了委派的概念,強調其在 C# 中的力量和多用途性。 他向觀眾保證,儘管有些術語讓人畏懼,但委派的基礎是簡單的。 Tim 的目標是破解委派的奧秘,並涵蓋如 func 和 action 的特殊型別。
應用程式演示概述
Tim 設置了一個演示應用程式以說明委派的使用。 解決方案包含三個專案:一個控制台 UI,Demo Library,和一個 WinForm UI。 最初的重點在於控制台 UI 和 Demo Library。
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 委派的使用。 這些是 Microsoft 提供的特殊型別委派,用於簡化與泛型的委派使用。
識別問題
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 和匿名方法。 觀看完整的影片以學習如何在您的專案中有效應用委派!

