委托(delegate)是一種升級版的“函數指針”。
-
一切皆地址
變量(數據)是以某個地址為起點的一段內存中存儲的值。比如我們聲明了一個變量a,則cpu會找到變量a指向的內存首地址,根據a變量的分配大小,獲取一整塊屬於a的內存。
函數(算法)是以某個地址為起點的一段內存中存儲的機器語言指令。cpu會根據一條條的機器指令來完成我們的算法邏輯。
-
直接調用和間接調用
直接調用:通過函數名來調用函數,cpu根據函數名直接獲得該函數的所在地址並開始執行。執行完畢后返回到調用者繼續向下執行。
間接調用:通過函數指針來調用函數,cpu根據函數指針存儲的值獲得函數所在地址並開始執行,執行完畢后返回調用者繼續向下執行。
我們可以看出直接調用和間接調用其實是一致的,都會獲取我們想調用的那個函數的所在地址。
-
強類型委托
Action委托 Func委托
這兩種簡單的委托為我們提供了基本的配置和約束~~ 以下為對這兩種委托適用的小例子:

using System; namespace LanguageLearn { internal class Program { static void Main(string[] args) { var calculator = new Calculator(); var action = new Action(calculator.Report); calculator.Report(); // 直接調用 action.Invoke(); // 間接調用 action(); // 函數指針調用形式 Func<int, int, int> funcAdd = new Func<int, int, int>(calculator.Add); var addResult = funcAdd.Invoke(10, 20); Console.WriteLine(addResult); Func<int, int, int> funcSub = new Func<int, int, int>(calculator.Sub); var subResult = funcSub.Invoke(10, 5); Console.WriteLine(subResult); Func<int, int, int> funcMuti = new Func<int, int, int>((x, y) => x * y); var mutiResult = funcMuti.Invoke(10, 5); Console.WriteLine(mutiResult); } } internal class Calculator { public void Report() { Console.WriteLine("I am a calculator"); } public int Add(int a, int b) { return a + b; } public int Sub(int a, int b) { return a - b; } } } //out put //I am a calculator //I am a calculator //I am a calculator //30 //5 //50
-
委托的聲明
委托是一種數據類型,並且它是一種類,所以它是引用類型,我把它理解成一種特別的引用類型,它是對函數方法類型的一種引用。
它的聲明方式與類的聲明不同,關鍵字為delegate。
注意聲明委托的位置,它不應成為嵌套類型。
委托與所委托的方法必須“類型兼容”,返回值需一致,入參的參數類型和參數個數也需要一致。

using System; namespace LanguageLearn { internal delegate double CalDelegate(double x, double y); internal class Program { static void Main(string[] args) { var calculator = new Calculator(); CalDelegate calDelegate1 = new CalDelegate(calculator.Add); CalDelegate calDelegate2 = new CalDelegate(calculator.Sub); CalDelegate calDelegate3 = new CalDelegate(calculator.Mul); CalDelegate calDelegate4 = new CalDelegate(calculator.Div); CalDelegate calDelegate5 = new CalDelegate((a, b) => a * b + 1); double a = 100; double b = 200; double c = 0; c = calDelegate1.Invoke(a, b); Console.WriteLine(c); c = calDelegate2.Invoke(a, b); Console.WriteLine(c); c = calDelegate3.Invoke(a, b); Console.WriteLine(c); c = calDelegate4.Invoke(a, b); Console.WriteLine(c); c = calDelegate5.Invoke(a, b); Console.WriteLine(c); } } internal class Calculator { public void Report() { Console.WriteLine("I am a calculator"); } public double Add(double a, double b) { return a + b; } public double Sub(double a, double b) { return a - b; } public double Mul(double a, double b) { return a * b; } public double Div(double a, double b) { return a / b; } } }
-
委托的一般使用
實例:將方法當作參數傳遞給另一個方法
解釋:我們在方法體的內部,適用參數傳遞進來的委托,間接的調用封裝在委托內的方法。形成了一種動態調用方法的結構。
正確使用1:模板方法,“借用” 指定的外部方法來產生結果 (在方法體中有一部分的空缺需要根據你傳遞的方法來完成)
-
-
-
- 相當於“填空題”
- 常位於代碼的中部
- 常具有返回值
-
-
正確使用2:回調(callback)方法,調用指定的外部方法 (類似於“提供商”,當你需要哪種服務的時候,可以根據不同的“提供商”來調用你需要的委托方法)
-
-
-
- 相當於“流水線” (我們在主調方法中,根據主調方法的條件來決定是否調用委托來回調委托封裝的方法)
- 常位於代碼的尾部
- 委托無返回值
-
-
模板方法實例:利用方法委托參數,我們可以在不改變某些核心邏輯代碼的情況下,利用委托作為模板,我們可以調用不同的方法,以達到不同的效果,在我理解,這是除了多態或工廠模式的另外一種減小了耦合度,遵循了開閉原則,的一種代碼高復用的實現形式。

using System; using System.Text.Json; namespace LanguageLearn { class Program { static void Main(string[] args) { ProductFactory productFactory = new ProductFactory(); WrapFactory wrapFactory = new WrapFactory(); Func<Product> func1 = new Func<Product>(productFactory.MakePizza); Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar); Box box1 = wrapFactory.WrapProduct(func1); Box box2 = wrapFactory.WrapProduct(func2); Console.WriteLine(JsonSerializer.Serialize<Box>(box1)); Console.WriteLine(JsonSerializer.Serialize<Box>(box2)); } } class Product { public string Name { get; set; } } class Box { public Product Product { get; set; } } class WrapFactory { public Box WrapProduct(Func<Product> GetProduct) { var box = new Box(); box.Product = GetProduct.Invoke(); return box; } } class ProductFactory { public Product MakePizza() { Product product = new Product(); product.Name = "Pizza"; return product; } public Product MakeToyCar() { Product product = new Product(); product.Name = "ToyCar"; return product; } } } // out put //{ "Product":{ "Name":"Pizza"} } //{ "Product":{ "Name":"ToyCar"} }
回調方法實例:根據方法中的執行條件,我們可以按需的執行回調方法,回調方法通常是一些Action委托,可用與做一些記錄或特殊方法的再次調用。

using System; using System.Text.Json; namespace LanguageLearn { class Program { static void Main(string[] args) { ProductFactory productFactory = new ProductFactory(); WrapFactory wrapFactory = new WrapFactory(); Func<Product> func1 = new Func<Product>(productFactory.MakePizza); Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar); Logger logger = new Logger(); Action<Product> log = new Action<Product>(logger.Log); Box box1 = wrapFactory.WrapProduct(func1, log); Box box2 = wrapFactory.WrapProduct(func2, log); Console.WriteLine(JsonSerializer.Serialize<Box>(box1)); Console.WriteLine(JsonSerializer.Serialize<Box>(box2)); } } class Logger { public void Log(Product product) { Console.WriteLine($"Product {product.Name} created at {DateTime.UtcNow}, price is {product.Price}"); } } class Product { public string Name { get; set; } public double Price { get; set; } } class Box { public Product Product { get; set; } } class WrapFactory { public Box WrapProduct(Func<Product> GetProduct, Action<Product> logCallBack) { var box = new Box(); var product = GetProduct.Invoke(); if (product.Price > 50) { logCallBack.Invoke(product); } box.Product = product; return box; } } class ProductFactory { public Product MakePizza() { Product product = new Product(); product.Name = "Pizza"; product.Price = 30; return product; } public Product MakeToyCar() { Product product = new Product(); product.Name = "ToyCar"; product.Price = 80; return product; } } } // out put // Product ToyCar created at 10/10/2021 6:54:13 AM, price is 80 //{ "Product":{ "Name":"Pizza","Price":30} } //{ "Product":{ "Name":"ToyCar","Price":80} }
-
委托的高階使用
委托的單播和多播調用
委托的隱式異步調用以及顯式異步調用

using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace LanguageLearn { class Program { static void Main(string[] args) { Student stu1 = new Student() { Id = 1, PenColor = ConsoleColor.Yellow }; Student stu2 = new Student() { Id = 2, PenColor = ConsoleColor.Green }; Student stu3 = new Student() { Id = 3, PenColor = ConsoleColor.Red }; // 多播委托 //Action action = new Action(stu1.DoHomeWork); //action += stu2.DoHomeWork; //action += stu3.DoHomeWork; Task task1 = new Task(new Action(stu1.DoHomeWork)); Task task2 = new Task(new Action(stu2.DoHomeWork)); Task task3 = new Task(new Action(stu3.DoHomeWork)); task1.Start(); task2.Start(); task3.Start(); // 顯式線程調用 //Action action1 = new Action(stu1.DoHomeWork); //Action action2 = new Action(stu2.DoHomeWork); //Action action3 = new Action(stu3.DoHomeWork); //Thread thread1 = new Thread(new ThreadStart(action1)); //Thread thread2 = new Thread(new ThreadStart(action2)); //Thread thread3 = new Thread(new ThreadStart(action3)); //thread1.Start(); //thread2.Start(); //thread3.Start(); // 隱式異步調用 在.NET FrameWork中被支持 //action1.BeginInvoke(null, null); //action2.BeginInvoke(null, null); //action3.BeginInvoke(null, null); for (int i = 0; i < 10; i++) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine($"Main thread count {i}"); Thread.Sleep(1000); } } } class Student { public int Id { get; set; } public ConsoleColor PenColor { get; set; } public void DoHomeWork() { for (int i = 1; i <= 3; i++) { Console.ForegroundColor = PenColor; Console.WriteLine($"Student {Id} doing homeworks {i} hour"); Thread.Sleep(1000); } } } }