引言
Delegate是Dotnet1.0的時候已經存在的特性了,但由於在實際工作中一直沒有機會使用Delegate這個特性,所以一直沒有對它作整理。這兩天,我再度翻閱了一些關於Delegate的資料,並開始正式整理這個C#中著名的特性。本文將由淺入深的談一下Delegate這個特性。
一.Delegate是什麼?
Delegate中文翻譯爲“委託”。Msdn中對Delegate的解釋如下:
C#中的委託類似於C或C++中的函數指針。使用委託使程序員可以將方法引用封裝在委託對象內。然後可以將該委託對象傳遞給可調用所引用方法的代碼,而不必在編譯時知道將調用哪個方法。與C或C++中的函數指針不同,委託是面向對象、類型安全的,並且是安全的。
如果你是第一次接觸Delegate這個概念,你可能會對上面這段文字感覺不知所云,不過不要緊,你可以先把Delegate認爲就是一個函數指針。
而當你面對一個虛無的概念時,最好的應對方法就是直接看實例。下面一個簡單的Delegate使用例子。
class Program
{
static void OtherClassMethod(){
Console.WriteLine("Delegate an other class's method");
}
static void Main(string[] args)
{
var test = new TestDelegate();
test.delegateMethod = new TestDelegate.DelegateMethod(test.NonStaticMethod);
test.delegateMethod += new TestDelegate.DelegateMethod(TestDelegate.StaticMethod);
test.delegateMethod += Program.OtherClassMethod;
test.RunDelegateMethods();
}
}
class TestDelegate
{
public delegate void DelegateMethod(); //聲明瞭一個Delegate Type
public DelegateMethod delegateMethod; //聲明瞭一個Delegate對象
public static void StaticMethod()
{
Console.WriteLine("Delegate a static method");
}
public void NonStaticMethod()
{
Console.WriteLine("Delegate a non-static method");
}
public void RunDelegateMethods()
{
if(delegateMethod != null){
Console.WriteLine("---------");
delegateMethod.Invoke();
Console.WriteLine("---------");
}
}
}
上面是一個Delegate的使用例子,運行看看結果吧。下面我稍微解釋一下:
【1】public delegate void DelegateMethod();這裏聲明瞭一個Delegate的類型,名爲DelegateMethod,這種Delegate類型可以搭載:返回值爲void,無傳入參數的函數。
【2】public DelegateMethod delegateMethod;這裏聲明瞭一個DelegateMethod的對象(即,聲明瞭某種Delegate類型的對象)。
區分:DelegateMethod是類型,delegateMethod是對象。
【3】爲什麼上面說Delegate可以看做是函數指針呢?看下面這段代碼:
test.delegateMethod = new TestDelegate.DelegateMethod(test.NonStaticMethod);
test.delegateMethod += new TestDelegate.DelegateMethod(TestDelegate.StaticMethod);
test.delegateMethod += Program.OtherClassMethod;
這裏delegateMethod搭載了3個函數,而且可以通過調用delegateMethod.Invoke();運行被搭載的函數。這就是Delegate可以看作爲函數指針的原因。上面這段代碼中,delegateMethod只能搭載:返回值爲void,無傳入參數的函數(見:NonStaticMethod,StaticMethod,OtherClassMethod的定義),這和Delegate類型聲明有關(見DelegateMethod的聲明:public delegate void DelegateMethod())。
【4】Delegate在搭載多個方法時,可以通過+=增加搭載的函數,也可以通過-=來去掉Delegate中的某個函數。
二.Delegate和C++中函數指針的區別
Delegate和C++中的函數指針很像,但如果深入對比,發現其實還是有區別的,區別主要有三個方面(參考Stanley B. Lippman的一篇文章)
1) 一個 delegate對象一次可以搭載多個方法(methods),而不是一次一個。當我們喚起一個搭載了多個方法(methods)的delegate,所有方法以其“被搭載到delegate對象的順序”被依次喚起。
2) 一個delegate對象所搭載的方法(methods)並不需要屬於同一個類別。一個delegate對象所搭載的所有方法(methods)必須具有相同的原型和形式。然而,這些方法(methods)可以即有static也有non-static,可以由一個或多個不同類別的成員組成。
3) 一個delegate type的聲明在本質上是創建了一個新的subtype instance,該 subtype 派生自 .NET library framework 的 abstract base classes Delegate 或 MulticastDelegate,它們提供一組public methods用以詢訪delegate對象或其搭載的方法(methods) ,與函數指針不同,委託是面向對象、類型安全並且安全的。
看完上面關於Delegate的介紹,相信大家對它也有所瞭解了,下面我們將進行更深入地討論!
三.Delegate什麼時候該用?
看完上面的介紹,你可以會有一些疑問,爲什麼會有Delegate?實際中什麼時候會用到?什麼時候應該去用? 在回答這些問題之前,大家可以先看看下面這段代碼:
class Program
{
static void Main(string[] args)
{
var car = new Car(15);
new Alerter(car);
car.Run(120);
}
}
class Car
{
public delegate void Notify(int value);
public event Notify notifier;
private int petrol = 0;
public int Petrol
{
get { return petrol; }
set
{
petrol = value;
if (petrol < 10) //當petrol的值小於10時,出發警報
{
if (notifier != null)
{
notifier.Invoke(Petrol);
}
}
}
}
public Car(int petrol)
{
Petrol = petrol;
}
public void Run(int speed)
{
int distance = 0;
while (Petrol > 0)
{
Thread.Sleep(500);
Petrol--;
distance += speed;
Console.WriteLine("Car is running... Distance is " + distance.ToString());
}
}
}
class Alerter
{
public Alerter(Car car)
{
car.notifier += new Car.Notify(NotEnoughPetrol);
}
public void NotEnoughPetrol(int value)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("You only have " + value.ToString() + " gallon petrol left!");
Console.ResetColor();
}
}
看完了上面的代碼後,你可能會問:爲什麼不在public int Petrol中直接調用Alerter.NotEnoughPetrol呢?因爲Car模塊和Alerter模塊本身是兩個獨立的子系統,如果直接調用,耦合性就會增加,這不是我們願意看到的。
其實以上的代碼是設計模式中的觀察者模式(觀察者模式又稱Source/Listener模式)的實現,當汽車在運行中汽油量<10時,警報器便會發出警報。在上面代碼中,Delegate相當於一個存放回調函數的函數指針,使用Delegate,我們可以非常方便地實現觀察者模式。而其實,在需要使用回調函數時,我們都可以考慮使用Delegate。
不知道你有沒有發現在上面的代碼中還有一個問題呢?
public event Notify notifier;
上面的代碼中,我們定義了一個Event,而事實上:
public Notify notifier;
這樣寫,也完全可以滿足我們的需求,這就引出了我們的另一個問題,Delegate和Event!
四.Delegate與Event
【1】Delegate和Event的關係
看微軟的代碼時,我們會發現Delegate和Event這兩個關鍵字經常會一起出現!究竟他們是什麼關係呢?
在Msdn中,有一段話描述Delegate和Event之間的關係,其實很簡單:
聲明事件:若要在類內聲明事件,首先必須聲明該事件的委託類型。
【2】Delegate和Event配合使用的效果
看下面幾幅圖,這是我從一個C#的Application程序截下來的:
從上圖看到,在響應圖形界面的操作中,我們用到了Event和Delegate,相信這也我們使用Event和Delegate最頻繁的地方了。這裏我還想羅嗦一下,平時需要我們自己寫代碼的界面事件響應函數,如:button_Click(…),其實都是回調函數,在自動生成的文件Form1.Designer.cs中,VS把事件和其對應的回調函數(即:button_Click(…)等)關聯起來,當觸發某事件時,對應的回調函數便會執行。
【3】“public Notify notifier”和“public event Notify notifier”的區別
關於這個問題,我們直接ildasm看看IL代碼吧:>
“public Notify notifier”的IL代碼,如圖:
“public event Notify notifier”的IL代碼,如圖:
差別其實已經很明顯了,“public Notify notifier”相當於Class裏面的Field,訪問級別是public,而“public eventNotify notifier”則相當於Property,訪問級別是private!由於以上的差別,他們在某些使用上,會稍有不同,詳細的可參考shensr寫的《delegate vs. event》。
五.Delegate中的Invoke與BeginInvoke方法
簡單說一下,Invoke與BeginInvoke都是執行Delegate裏的搭載函數,而不同的是:Invoke是一個同步方法,BeginInvoke是一個異步方法。關於這個,有一篇文章《Invoke and BeginInvoke》,對此介紹的比較詳細,這裏就不多說了。
六.小結
回顧一下,到底什麼時候我們可能會用到Delegate:
【1】.當我們在C#中需要類似函數指針這樣東西時。
【2】.當我們需要使用回調函數的時候。
【3】.需要異步調用的時候。
【4】.實現觀察者模式的時候。
【5】.處理事件響應的時候。
以上內容均爲個人看法,如果有錯漏,請各位及時指出:>