1. 基本瞭解

1.1 委託簡述

官方文件

委託是一種引用型別,表示對具有特定引數列表和返回型別的方法的引用,用於將方法作為引數傳遞給其他方法,可將任何可訪問類或結構中與委託型別匹配的任何方法分配給委託

其它說明

委託在IL中就是一個類(本質上是類),繼承與System.MulticastDelegate類(特殊類)

委託是一個類,它定義了方法的型別,使得可以將方法當作另一個方法的引數來進行傳遞

委託型別

自定義委託:Delegate

系統內建委託:ActionFunc

委託宣告

可以宣告在類的外部也可以在內部,在IL中,無論在內外都會編譯到類的內部

委託在例項化時,需要傳入一個方法,此方法返回值,引數(型別,個數,順序)與委託一致

1.2 使用步驟

  • 宣告一個委託
  • 委託的例項化,傳入指定方法
  • 呼叫執行

2. Delegate委託

Delegate:常用到的一種宣告,且至少0個引數,至多32個引數,可以無返回值,也可以指定返回值型別

2.1 示例一:無參,無返回值

  1. // 1.宣告委託
  2. public delegate void NoReturnNoPare();
  3. // 2.準備委託執行方法
  4. public void Show()
  5. {
  6. Console.WriteLine("無參,無返回值");
  7. }
  8. // 3.例項,呼叫委託
  9. public void Start()
  10. {
  11. NoReturnNoPare d1 = new NoReturnNoPare(this.Show);
  12. d1.Invoke();
  13. }

2.2 示例二:有參,無返回值

  1. // 1.宣告委託
  2. public delegate void NoReturnWithPare(int x, int y);
  3. // 2.準備委託執行方法
  4. public void Show(int x,int y)
  5. {
  6. Console.WriteLine("有參,無返回值");
  7. }
  8. // 3.例項,呼叫委託
  9. public void Start()
  10. {
  11. NoReturnWithPare d2 = new NoReturnWithPare(this.Show);
  12. d2.Invoke(1,2);
  13. }

2.3 示例三:有參,有返回值

  1. // 1.宣告委託
  2. public delegate int WithReturnWithPare(int x, int y);
  3. // 2.準備委託執行方法
  4. public int Show(int x, int y)
  5. {
  6. return x + y;
  7. }
  8. // 3.例項,呼叫委託
  9. public void Start()
  10. {
  11. WithReturnWithPare d2 = new WithReturnWithPare(this.Show);
  12. // 返回值型別,編譯器會自動推斷
  13. int IResult = d2.Invoke(1, 2);
  14. Console.WriteLine(IResult);
  15. }

3. Action委託

Action是系統內建委託(無需宣告),是無返回值的泛型委託,至少0個引數,至多16個引數,且無返回值

3.1 示例一:無參,無返回值

  1. // 1.定義執行方法
  2. public void Show()
  3. {
  4. Console.WriteLine("無參,無返回值");
  5. }
  6. // 2.呼叫執行
  7. public void Start()
  8. {
  9. Action action = new Action(this.Show);
  10. // Action<int, int> action = this.Show;
  11. action.Invoke();
  12. }

3.2 示例二:有參,無返回值

  1. // 1.定義執行方法
  2. public void Show(int x, int y)
  3. {
  4. Console.WriteLine("有參,無返回值");
  5. }
  6. // 2.呼叫執行
  7. public void Start()
  8. {
  9. Action<int, int> action = new Action<int,int>(this.Show);
  10. // Action<int, int> action = this.Show;
  11. action.Invoke(1,2);
  12. }

3.3 示例三:使用 lambda 表示式

  1. Action<int, int> action = (x, y) => { };
  2. action.Invoke(1, 2);

3.4 示例四:將委託作為方法引數

  1. public void Start()
  2. {
  3. Action<int> action = (x) => { Console.WriteLine(x); };
  4. Show(action, 2);
  5. }
  6. public void Show<T>(Action<T> ac, T inputParam)
  7. {
  8. ac(inputParam);
  9. }

4. Func委託

Func是有返回值的泛型委託,至少0個引數,至多16個引數,根據返回值泛型返回;必須有返回值,不可void,且最後一位泛型型別,為返回值型別

4.1 示例一:無參,有返回值

  1. public void Start()
  2. {
  3. Func<string> func = new Func<string>(this.Show);
  4. string IResult = func.Invoke();
  5. Console.WriteLine(IResult);
  6. }
  7. public string Show()
  8. {
  9. return "libai";
  10. }

4.2 示例二:有參,有返回值

  1. public void Start()
  2. {
  3. Func<int, string> func = new Func<int, string>(this.Show);
  4. string IResult = func.Invoke(1);
  5. Console.WriteLine(IResult);
  6. }
  7. public string Show(int i)
  8. {
  9. return "libai\t" + i;
  10. }

4.3 示例三:使用lambda表示式

  1. public void Start()
  2. {
  3. Func<int> func1 = () => { return 1; };
  4. int IResultInt = func1.Invoke();
  5. Console.WriteLine(IResultInt);
  6. Func<int, string> func2 = (i) => { return i.ToString(); };
  7. string IResult = func2.Invoke(1);
  8. Console.WriteLine(IResult);
  9. }

4.4 示例四:將委託作為方法引數

例子一:簡化

  1. public void Start()
  2. {
  3. Func<int, int, int> func = (x, y) => { return x + y; };
  4. int IResultInt = Test(func, 1, 2);
  5. Console.WriteLine(IResultInt);
  6. }
  7. public int Test<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b)
  8. {
  9. return func(a, b);
  10. }

示例二:一般寫法

  1. static void Main(string[] args)
  2. {
  3. Console.WriteLine(Test<int,int>(Fun,100,200));
  4. Console.ReadKey();
  5. }
  6. public static int Test<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b)
  7. {
  8. return func(a, b);
  9. }
  10. private static int Fun(int a, int b)
  11. {
  12. return a + b;
  13. }

5. 鏈式委託

5.1 文件說明

官方文件

委託物件的一個有用屬性在於可通過使用 + 運算子將多個物件分配到一個委託例項,多播委託包含已分配委託列表,此多播委託被呼叫時會依次呼叫列表中的委託;僅可合併型別相同的委託

  • - 運算子可用於從多播委託中刪除元件委託,順序,從下至上(委託列表中沒有移除的委託時不會報錯)
  • + 運算子可用於將委託元件新增到委託列表,順序,從上而下

在執行有返回值的委託鏈時,只能得到最後一個委託的結果

其它文件

委託鏈(多播委託)是一個由委託組成的連結串列,而不是一個新的東西,所有的自定義委託都直接整合自System.MulticastDelegate型別,這個型別即是為委託鏈而設計的

鏈式委託是指一個委託的連結串列,而不是指另外一類特殊的委託,當執行鏈上的一個方法時,後續委託將會被依此執行

System.MuticastDelegate定義了對鏈式委託的支援,在System.Delegate的基礎上,增加了一個指向後續委託的指標,這樣就實現了一個簡單的連結串列結構

5.2 示例一:統一執行

  1. public void Start()
  2. {
  3. NoReturnWithPare noReturnWith = new NoReturnWithPare(this.Fun1);
  4. noReturnWith += this.Fun2;
  5. noReturnWith += this.Fun1;
  6. noReturnWith.Invoke(1, 2);
  7. }
  8. public void Fun1(int x, int y)
  9. {
  10. Console.WriteLine("Fun1:\t" + x + y);
  11. }
  12. public void Fun2(int x, int y)
  13. {
  14. Console.WriteLine("Fun2:\t" + x + y);
  15. }

5.3 示例二:逐個執行

注意:逐個執行時,單項不能用var宣告,必須使用委託的具體型別

逐個指定,返回值,問題,b委託使用a的返回值?

  1. public void Start()
  2. {
  3. NoReturnWithPare noReturnWith = new NoReturnWithPare(this.Fun1);
  4. noReturnWith += this.Fun2;
  5. noReturnWith += this.Fun1;
  6. foreach (NoReturnWithPare item in noReturnWith.GetInvocationList())
  7. {
  8. item.Invoke(1,2);
  9. }
  10. }
  11. public void Fun1(int x, int y)
  12. {
  13. Console.WriteLine("Fun1:\t" + x + y);
  14. }
  15. public void Fun2(int x, int y)
  16. {
  17. Console.WriteLine("Fun2:\t" + x + y);
  18. }

5.4 示例三:責任鏈模式

典型案例:用程式碼模擬,貓叫了,狗叫了,然後老鼠跑了

普通實現

  1. using System;
  2. namespace de2
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. Cat cat = new Cat();
  9. cat.Miao();
  10. }
  11. }
  12. public class Cat
  13. {
  14. public void Miao()
  15. {
  16. Console.WriteLine("貓叫了");
  17. new Dog().Wang();
  18. new Mouse().Run();
  19. }
  20. }
  21. public class Dog
  22. {
  23. public void Wang()
  24. {
  25. Console.WriteLine("狗叫了");
  26. }
  27. }
  28. public class Mouse
  29. {
  30. public void Run()
  31. {
  32. Console.WriteLine("老鼠跑了");
  33. }
  34. }
  35. }

使用責任鏈模式實現(委託)

  1. using System;
  2. namespace de2
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. Cat cat = new Cat();
  9. cat.miaoAction += new Dog().Wang;
  10. cat.miaoAction += new Mouse().Run;
  11. cat.MiaoDelegate();
  12. }
  13. }
  14. public class Cat
  15. {
  16. public void Miao()
  17. {
  18. Console.WriteLine("貓叫了");
  19. }
  20. public Action miaoAction;
  21. public void MiaoDelegate()
  22. {
  23. this.Miao();
  24. this.miaoAction.Invoke();
  25. }
  26. }
  27. public class Dog
  28. {
  29. public void Wang()
  30. {
  31. Console.WriteLine("狗叫了");
  32. }
  33. }
  34. public class Mouse
  35. {
  36. public void Run()
  37. {
  38. Console.WriteLine("老鼠跑了");
  39. }
  40. }
  41. }

使用責任鏈模式實現(抽象方法)

  1. using System;
  2. using System.Collections.Generic;
  3. namespace de2
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. Cat cat = new Cat();
  10. cat.Add(new Dog());
  11. cat.Add(new Mouse());
  12. cat.AbsServer();
  13. }
  14. }
  15. public interface IAbsServer
  16. {
  17. void Do();
  18. }
  19. public class Cat : IAbsServer
  20. {
  21. private List<IAbsServer> list = new List<IAbsServer>();
  22. public void Add(IAbsServer absServer)
  23. {
  24. list.Add(absServer);
  25. }
  26. public void AbsServer()
  27. {
  28. this.Do();
  29. foreach (var item in list)
  30. {
  31. item.Do();
  32. }
  33. }
  34. public void Miao()
  35. {
  36. Console.WriteLine("貓叫了");
  37. }
  38. public void Do()
  39. {
  40. this.Miao();
  41. }
  42. }
  43. public class Dog : IAbsServer
  44. {
  45. public void Do()
  46. {
  47. this.Wang();
  48. }
  49. public void Wang()
  50. {
  51. Console.WriteLine("狗叫了");
  52. }
  53. }
  54. public class Mouse : IAbsServer
  55. {
  56. public void Do()
  57. {
  58. this.Run();
  59. }
  60. public void Run()
  61. {
  62. Console.WriteLine("老鼠跑了");
  63. }
  64. }
  65. }

使用責任鏈模式實現(事件)

  1. using System;
  2. namespace de2
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. Cat cat = new Cat();
  9. cat.miaoEvent += new Dog().Wang;
  10. cat.miaoEvent += new Mouse().Run;
  11. cat.MiaoEvent();
  12. }
  13. }
  14. public class Cat
  15. {
  16. public void Miao()
  17. {
  18. Console.WriteLine("貓叫了");
  19. }
  20. /// <summary>
  21. /// 事件,只能在事件所在類(本身類,子類不可)的內部 Invoke 執行
  22. /// </summary>
  23. public event Action miaoEvent;
  24. public void MiaoEvent()
  25. {
  26. this.Miao();
  27. this.miaoEvent.Invoke();
  28. }
  29. }
  30. public class Dog
  31. {
  32. public void Wang()
  33. {
  34. Console.WriteLine("狗叫了");
  35. }
  36. }
  37. public class Mouse
  38. {
  39. public void Run()
  40. {
  41. Console.WriteLine("老鼠跑了");
  42. }
  43. }
  44. }

5.5 補充說明

鏈式委託的執行順序是:按照委託鏈上的順醋從當前委託開始依次往後執行,如果有需要可以使用GetInvocationList()方法來獲得委託鏈上所有需要執行的委託,並且按照任何希望的順序去逐個執行(Invoke

委託可以是帶有返回值的方法,但多餘一個帶返回值的方法被新增到委託鏈中時,程式設計師需要手動地呼叫委託鏈上的每個方法,否則委託使用者智慧得到委託鏈上最後一個被執行的方法的返回值

委託的應用場合通常是任務的執行者把細節工作進行再分配,執行者確切地知道什麼工作將要被執行,但卻把執行細節委託給其他元件、方法或者程式集

6. 委託事件

事件(Event):是委託的例項,在定義委託是加了enevt 關鍵字

enevt 關鍵字,限定許可權,只能在事件所在類中呼叫事件

6.1 示例一:自定義標準事件

模擬:使用者訂閱手機降價事件,當降價時用於購買手機

  1. using System;
  2. namespace de3
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. Phone phone = new Phone
  9. {
  10. name = "vivo",
  11. Price = 1999
  12. };
  13. phone.DiscountEventHandler += new User() { name = "李白" }.Buy;
  14. phone.Price -= 400;
  15. }
  16. }
  17. // 事件額外資訊
  18. public class EventPara
  19. {
  20. public int oValue { get; set; }
  21. public int nValue { get; set; }
  22. }
  23. public delegate void CostomEventHandler(object sender, EventPara para);
  24. // 手機,釋出者,釋出事件並且在滿足條件情況下執行事件
  25. public class Phone
  26. {
  27. public string name { get; set; }
  28. private int price;
  29. public int Price
  30. {
  31. set
  32. {
  33. if (value < this.price)
  34. {
  35. this.DiscountEventHandler?.Invoke(this, new EventPara
  36. {
  37. oValue = this.price,
  38. nValue = value
  39. });
  40. }
  41. this.price = value;
  42. }
  43. get { return this.price; }
  44. }
  45. public event CostomEventHandler DiscountEventHandler;
  46. }
  47. // 訂戶,關注事件,事件發生後執行動作
  48. public class User
  49. {
  50. public string name { get; set; }
  51. // 買手機
  52. public void Buy(object sender, EventPara para)
  53. {
  54. Phone phone = (Phone)sender;
  55. Console.WriteLine($"手機:{phone.name}\t打折前:{para.oValue}\t打折後:{para.nValue}");
  56. Console.WriteLine("購買手機!");
  57. }
  58. }
  59. }

標註:委託事件實際應用還不太熟,示例做參考即可

7. 擴充套件補充

7.1 委託的內部結構

IL語言的無參無返回值的委託結構(編譯後)

  1. .class nested public auto ansi sealed NoReturnNoPare
  2. extends [mscorlib]System.MulticastDelegate
  3. {
  4. // Methods
  5. .method public hidebysig specialname rtspecialname
  6. instance void .ctor (
  7. object 'object',
  8. native int 'method'
  9. ) runtime managed
  10. {
  11. } // end of method NoReturnNoPare::.ctor
  12. .method public hidebysig newslot virtual
  13. instance void Invoke () runtime managed
  14. {
  15. } // end of method NoReturnNoPare::Invoke
  16. .method public hidebysig newslot virtual
  17. instance class [mscorlib]System.IAsyncResult BeginInvoke (
  18. class [mscorlib]System.AsyncCallback callback,
  19. object 'object'
  20. ) runtime managed
  21. {
  22. } // end of method NoReturnNoPare::BeginInvoke
  23. .method public hidebysig newslot virtual
  24. instance void EndInvoke (
  25. class [mscorlib]System.IAsyncResult result
  26. ) runtime managed
  27. {
  28. } // end of method NoReturnNoPare::EndInvoke
  29. } // end of class NoReturnNoPare

7.2 呼叫委託

使用委託例項呼叫,引數寫在括號中

  1. NoReturnNoPare d1 = new NoReturnNoPare(this.Show);
  2. d1();

使用例項的Invoke()方法呼叫,引數寫在方法中

  1. NoReturnNoPare d1 = new NoReturnNoPare(this.Show);
  2. d1.Invoke();

7.3 Predicate<T>委託

說明:不常用,僅作為了解(看個人情況)

Predicate是返回bool型的泛型委託,至少1個引數,至多1個引數,返回值固定為bool

官方示例

  1. using System;
  2. using System.Drawing;
  3. public class Example
  4. {
  5. public static void Main()
  6. {
  7. Point[] points = { new Point(100, 200),
  8. new Point(150, 250), new Point(250, 375),
  9. new Point(275, 395), new Point(295, 450) };
  10. Predicate<Point> predicate = FindPoints;
  11. Point first = Array.Find(points, predicate);
  12. Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
  13. }
  14. private static bool FindPoints(Point obj)
  15. {
  16. return obj.X * obj.Y > 100000;
  17. }
  18. }