一、引用方法
委托是尋址方法的.NET版本。委托是類型安全的類,它定義了返回類型和參數的類型。委托是對方法的引用,也可以對多個方法進行引用,委托可以理解為指向方法地址的指針。
如:delegate int ReturnIntHandler(int a,int b);//int是返回類型,a和b是引用類型,這是委托執行的方法必須滿足如下格式:int method(int param1,int param2);
二、委托
當要把方法傳遞給其它方法時,需要使用委托。委托是一種特殊類型的對象,其特殊之處在於,我們以前定義的所有對象都包含數據,而委托包含的只是一個或多個方法的地址。
1、聲明委托
委托使用關鍵字 delegate 進行定義。
定義委托基本上就是定義一個新類,所以可以在定義類的任何相同地方定義委托。可以在委托定義上應用常見的訪問修飾符:public、private、protected等。其訪問作用域也雷同於類。
2、使用委托
為了減少輸入量,只需要委托實例,就可以只傳遞地址的名稱。這稱為委托推斷。
delegate int CalculateMethodInvoker(int x, int y); class Program { static void Main(string[] args) { //創建委托對象 CalculateMethodInvoker calculateMethodInvoker = CalculateHelper.Sum; //等同於CalculateMethodInvoker calculateMethodInvoker = new CalculateMethodInvoker(CalculateHelper.Sum);//委托的實例化,指向Sum方法,其實委托也是可以定義,實例化調用的,不只是lambda表達式的調用方式 int x = 100, y = 200; Console.WriteLine("x,y相加:{0}", Calculate(calculateMethodInvoker, x, y)); calculateMethodInvoker = CalculateHelper.Multiply; Console.WriteLine("x,y相乘:{0}", Calculate(calculateMethodInvoker, x, y)); Console.ReadKey(); } public static int Calculate(CalculateMethodInvoker calculateMethodInvoker, int x, int y) { //return calculateMethodInvoker(x, y); //return calculateMethodInvoker.Invoke(x, y);//是不是當前線程都可以 IAsyncResult result = calculateMethodInvoker.BeginInvoke(x, y, null, calculateMethodInvoker);//異步,這里只是做展示,EndInvoke類似於async中的await,這里不能實現異步效果 return calculateMethodInvoker.EndInvoke(result); } } public class CalculateHelper { public static int Sum(int x, int y) { return x + y; } public static int Multiply(int x, int y) { return x * y; } }
3、Action<T>和Func<T>委托
除了為每個參數和返回類型定義一個新的委托類型外,還可以使用Action<T>和Func<T>委托。
泛型Action<T>委托表示引用一個void返回類型的方法,沒有泛型參數的Action類可調用沒有參數的方法,如Action 等價於 delegate void mydelegate; Action<int,int>等價於delegate void mydelegate(int param1,int param2);
泛型Func<T>委托表示引用一個有返回值的方法,泛型的最后一個參數時Func的返回值類型,如Func<int,int,bool>,等價於delegate bool mydelegate(int param1,int param2);
4、多播委托
委托也可以包含多個方法。這種委托成為多播委托。如果調用多播委托,就可以按順序連續調用多個方法。為此,委托的簽名就必須返回void;否則,就只能得到委托調用的最后一個方法的結果。多播委托識別運算符“-”、“+”、“-=”、“+=”以從委托中增加或刪除方法調用。
如:
class Program { static void Main(string[] args) { Action<int, int> calFunc = CalculateHelper.Sum; calFunc += CalculateHelper.Multiply;//多播加 int x = 100, y = 200; Calculate(calFunc, x, y); calFunc =calFunc- CalculateHelper.Multiply;//多播減 Calculate(calFunc, x, y); Console.ReadKey(); } public static void Calculate(Action<int, int> calculateMethodInvoker, int x, int y) { Console.WriteLine("運行結果:"); //calculateMethodInvoker(x, y); foreach (Action<int, int> item in calculateMethodInvoker.GetInvocationList())//遍歷,這里需要轉為為當前類型委托 { try { item(x, y);//執行委托指向方法 } catch (Exception ex) { Console.WriteLine(ex.Message); } } } }
如果通過委托調用的其中一個方法拋出異常,整個迭代就會停止。解決的方法是,使用Delegate類中定義的GetInvocationList()方法獲取Delegate對象數組,再使用循環遍歷執行,在過程中捕獲異常,來繼續下一次迭代。
5、匿名方法
匿名方法是用作委托的參數的一段代碼。
如:
Action<int, int> calFunc = delegate (int i, int j) { Console.WriteLine("x,y相加:{0}", i + j); };
在匿名方法中不可使用跳轉語句(break、goto或continue),在匿名方法內部不能訪問不安全代碼,不能訪問在匿名方法外部使用的ref和out參數。
三、lambda表達式
C#3.0后,可以使用lambda把實現代碼賦予委托,只要有委托參數類型的地方,就可以使用lambda表達式。
如:
Action<int, int> calFunc = (i, j) => { Console.WriteLine("x,y相加:{0}", i + j); };
1、參數
lambda表達式有幾種定義參數的方式。如果只有一個參數,只寫出參數名就足夠了。如果除一個參數以外,需要圓括號把參數名括起來。
例子:
Action<int> one = i => { //method body }; Action<int, int> two = (i, j) => { //method body };
2、多行代碼
如果lambda表示只有一條語句,在方法塊內就不需要花括號和return語句,因為編譯器會隱式添加return。
如:
Func<int> lambdaOne = () => 0;
如果實現代碼超過一行,就需要使用return語句顯式返回。
如:
{ int i = 0; i++; ++i; return i; };
3、閉包
通過lambda表達式可以訪問lambda表達式塊外部的變量。這稱為閉包。
如:
int param = 10; Action<int> lambdaSecond = (i) => { Console.WriteLine(i + param); }; lambdaSecond(3); Console.ReadKey();
四、事件
事件基於委托,為委托提供了一種發布/訂閱機制。
如:
class Program { static void Main(string[] args) { AlarmClock alarmClock = new AlarmClock(); Student zsStudent = new Student("張三"); alarmClock.ItsGetUpClockEvent += zsStudent.ItsGetUpClock; alarmClock.ItsGetUpClock(); Student lsStudent = new Student("李四"); //WeakEventManager<AlarmClock, EventArgs>.AddHandler(alarmClock, "ItsGetUpClockEvent", lsStudent.ItsGetUpClock); ////弱事件,System.Windows,WPF中經常用到 //alarmClock.ItsGetUpClock(); Console.ReadKey(); } } //事件發布類 public class AlarmClock { public event EventHandler<EventArgs> ItsGetUpClockEvent; public void ItsGetUpClock() { Console.WriteLine("時間到,起床了!"); ItsGetUpClockEvent?.Invoke(this, new EventArgs());//判斷是否訂閱事件 } } //事件偵聽類 public class Student { public string Name { get; set; } public Student(string name) { this.Name = name; } public void ItsGetUpClock(object sender, EventArgs e) { Console.WriteLine("{0}關掉鬧鍾,起床了。", Name); } }
事件最常用的地方是Winform和Wpf窗體中,而Invoke的經典使用場景如下(非當前線程更改窗體文本):
private void ShowExecLog(string log) { if (this.richTextBox1.InvokeRequired)//判斷是否是當前線程 { this.richTextBox1.Invoke(new ShowLogHandler(ShowLog), log); } else { this.richTextBox1.Text += log; } } public void ShowLog(string log) { this.richTextBox1.Text += log; } public delegate void ShowLogHandler(string log);