本篇已收錄至《C#圖解教程》讀書筆記目錄貼,點擊訪問該目錄可獲取更多內容。
一、委托初窺:一個擁有方法的對象
(1)本質:持有一個或多個方法的對象;委托和典型的對象不同,執行委托實際上是執行它所“持有”的方法。如果從C++的角度來理解委托,可以將其理解為一個類型安全的、面向對象的函數指針。
(2)如何使用委托?
①聲明委托類型(delegate關鍵字)
②使用該委托類型聲明一個委托變量
③為委托類型增加方法
④調用委托執行方法
(3)委托的恆定性:
組合委托、為委托+=增加方法以及為委托-=移除方法讓我們看起來像是委托被修改了,其實它們並沒有被修改。事實上,委托是恆定的。
在為委托增加和移除方法時實際發生的是創建了一個新的委托,其調用列表是增加和移除后的方法結果。
(4)委托實例:
①簡單帶參數委托DEMO
delegate void MyDel(int value); //聲明委托類型 class Program { void PrintLow(int value) { Console.WriteLine("{0} - LowValue", value); } void PrintHigh(int value) { Console.WriteLine("{0} - HighValue", value); } static void Main(string[] args) { Program program = new Program(); MyDel myDel; //聲明委托類型 //獲取0~99之間的一個隨機數 Random random = new Random(); int randomValue = random.Next(99); //創建一個包含具體方法的委托對象並將其賦值給myDel變量 myDel = randomValue < 50 ? new MyDel(program.PrintLow) : new MyDel(program.PrintHigh); //執行委托 myDel(randomValue); Console.ReadKey(); } }
②簡單無參數多方法列表委托DEMO
delegate void PrintFunction(); class Test { public void Print1() { Console.WriteLine( "Print1 -- instance" ); } public static void Print2() { Console.WriteLine( "Print2 -- static" ); } } class Program { static void Main() { Test t = new Test(); PrintFunction pf; pf = t.Print1; pf += Test.Print2; pf += t.Print1; pf += Test.Print2; if ( pf != null ) { pf(); } else { Console.WriteLine( "Delegate is empty" ); } } }
③帶返回值的委托DEMO
delegate int MyDel(); class MyClass { int IntValue = 5; public int Add2() { IntValue += 2; return IntValue; } public int Add3() { IntValue += 3; return IntValue; } } class Program { static void Main() { MyClass mc = new MyClass(); MyDel mDel = mc.Add2; mDel += mc.Add3; mDel += mc.Add2; Console.WriteLine( "Value: {0}", mDel() ); } }
二、匿名方法:不好意思,我匿了
在委托所持有的方法中,如果某個方法只被使用一次,這種情況下,除了創建委托語法的需要,沒有必要創建獨立的具名方法。因此,匿名方法應運而生。
匿名方法是在初始化委托時內聯(inline)聲明的方法。
下面來看看在兩個版本的代碼:具名方法和匿名方法的比較,匿名方法是不是簡潔得多?
①具名參數

using System; class Program { public static int Add20( int x ) { return x + 20; } delegate int OtherDel( int InParam ); static void Main() { OtherDel del = Add20; Console.WriteLine( "{0}", del( 5 ) ); Console.WriteLine( "{0}", del( 6 ) ); } }
②匿名參數

using System; class Program { delegate int OtherDel(int InParam); static void Main() { OtherDel del = delegate(int x) { return x + 20; }; Console.WriteLine("{0}", del(5)); Console.WriteLine("{0}", del(6)); } }
三、Lambda表達式:好吃的語法糖
(1)本質:簡化語法的”語法糖“;
Lambda來源:1920年到1930年期間,數學家Alonzo Church等人發明了Lambda積分。Lambda積分是用於表示函數的一套系統,它使用希臘字母Lambda(λ)來表示無名函數。近年來,函數式編程語言(如Lisp)使用這個術語來表示可以直接描述函數定義的表達式,表達式不再需要有名字了。
(2)要點:
①Lambda表達式中的參數列表(參數數量、類型和位置)必須與委托相匹配;
②表達式中的參數列表不一定需要包含類型,除非委托有ref或out關鍵字(此時必須顯示聲明);
③如果沒有參數,必須使用一組空的圓括號;
(3)語法:
四、事件初窺:發布者和訂閱者模式
發布者訂閱者模式定義了一種一對多的依賴關系,讓多個訂閱者對象同時監聽某一個主題對象。這個主題對象在自身狀態變化時,會通知所有訂閱者對象,使它們能夠自動更新自己的狀態。
由訂閱者提供的方法稱為回調方法,因為發布者通過執行這些方法來”往回調用訂閱者的方法“。還可以將它們稱為事件處理程序,因為它們是為處理事件而調用的代碼。
下面通過一段經典的代碼來看看這個模式的應用:
using System; delegate void Handler(); class Incrementer { public event Handler CountedADozen; public void DoCount() { for ( int i=1; i < 100; i++ ) if ( i % 12 == 0 && CountedADozen != null ) CountedADozen(); } } class Dozens { public int DozensCount { get; private set; } public Dozens( Incrementer incrementer ) { DozensCount = 0; incrementer.CountedADozen += IncrementDozensCount; } void IncrementDozensCount() { DozensCount++; } } class Program { static void Main() { Incrementer incrementer = new Incrementer(); Dozens dozensCounter = new Dozens( incrementer ); incrementer.DoCount(); Console.WriteLine( "Number of dozens = {0}", dozensCounter.DozensCount ); } }
五、事件全過程:聲明、訂閱和觸發
(1)聲明事件:
①事件聲明在一個類中;
②附加的方法需與委托類型的簽名和返回類型匹配;
③聲明為public;
④無法new;
(2)訂閱事件:
①使用+=為事件增加事件處理程序;
②可以使用匿名方法和Lambda表達式;
(3)觸發事件:
①使用事件名稱,后面跟的參數列表包含在圓括號中;
②參數列表必須與事件的委托類型相匹配;
六、走向標准之路:EventHandler
程序的異步處理是使用C#事件的絕佳場景。Windows GUI廣泛地使用了事件,對於事件的使用,.NET框架提供了一個標准模式:EventHandler委托類型。
(1)第一個參數保存觸發事件的對象的引用(object類型,可以匹配任何類型的實例);
(2)第二個參數保存狀態信息(EventArgs類的實例),指明什么程序適用於該應用程序;
(3)返回類型為void;
現在我們來重構剛剛的訂閱者類,使用標准的EventHandler委托類型:
class Dozens { public int DozensCount { get; private set; } public Dozens( Incrementer incrementer ) { DozensCount = 0; incrementer.CountedADozen += IncrementDozensCount; } void IncrementDozensCount( object source, EventArgs e ) { DozensCount++; } }
那么,剛剛看到為了保持標准模式,我們只能有兩個參數,第一個是觸發事件的對象引用,第二個是EventArgs類的實例,如何在事件中傳遞數據呢?答案肯定是在第二個參數上找到切入點。我們可以聲明一個派生自EventArgs的子類,在其中聲明我們要傳遞的參數所對應的屬性來保存我們需要傳入的數據。TIPS:這個自定義子類的名稱建議以EventArgs結尾。
public class IncrementerEventArgs : EventArgs { public int IterationCount { get; set; } }
既然使用了自定義類,那么在事件的其他幾部分中要使用該自定義類還必須改為泛型委托和聲明自定義類對象。
class Incrementer { public event EventHandler<IncrementerEventArgs> CountedADozen; public void DoCount() { IncrementerEventArgs args = new IncrementerEventArgs(); for ( int i=1; i < 100; i++ ) if ( i % 12 == 0 && CountedADozen != null ) { args.IterationCount = i; CountedADozen( this, args ); } } }
為了在執行程序中獲取到傳遞的數據值,便可以直接通過派生自EventArgs的自定義類的屬性的到。
class Dozens { public int DozensCount { get; private set; } public Dozens( Incrementer incrementer ) { DozensCount = 0; incrementer.CountedADozen += IncrementDozensCount; } void IncrementDozensCount( object source, IncrementerEventArgs e ) { Console.WriteLine( "Incremented at iteration: {0} in {1}", e.IterationCount, source.ToString() ); DozensCount++; } }
本章思維導圖
附件
思維導圖(jpg、pdf以及mmap源文件)下載:http://pan.baidu.com/s/1hqA7KH2