(原創:http://hi.baidu.com/grayworm)
代理與事件是DotNet的兩個重要概念,但好多朋友感覺沒有這兩個概念照樣能夠進行常規的DotNet開發。其實深入理解這兩個概念對DotNet研究非常重要,尤其在WCSF的開發時,如果不理解這兩個概念那你就很難把View層和Presenter層的代碼進行分離。
以前從網上看過幾篇這方面的文章,總感覺有點晦澀難懂,希望這篇文章能對大家理解代理與事件有所幫助。
一、代理
首先我們要弄清代理是個什么東西。別讓一串翻譯過來的概念把大家搞暈了頭。
有的文章把代理稱委托、代表等,其實它們是一個東西,英文表述都是“Delegate”。由於沒有一本權威的書來規范這個概念,所以現在網上對它的稱謂不一。本文我將以“代理”來稱謂Delegate。
代理是什么呢?我認為“代理就是用來定義指向方法的引用”。下面我們就通過類來理解代理。
如:
Ren r = new Ren("車延祿");
上面的代碼,就是使用Ren這個類定義了一個指“車延祿”這個對象實例的一個引用。
也可以這樣理解:用Ren類定義的變量r,指向一個“車延祿”對象的實例。
類所定義的變量指向的是一個對象,代理所定義的變量指向的是個方法,當然這個方法可以是靜態方法也可以是實例方法。對代理引用的調用就是對代理所指向方法的調用。
1.代理聲明的語法:
[public/private] delegate <返回值類型> <代理名稱>(<參數列表>);
[public/private]:訪問修飾符。
delegate:代理聲明關鍵定,相當於類聲明的Class關鍵定
<返回值類型>:代理所指向的方法的返回值類型
<代理名稱>:代理類型的名稱
<參數列表>:代理所的指向的方法的參數列表。
要想使代理對象能夠指向一個方法,那這個方法的要滿足兩個條件
a.方法返回類型要與delegate聲明中的“返回值類型”一致。
b.方法的形參形表要與delegate聲明中的“參數列表”一致。
如:
delegate void MyDelegate(string str,int index);
該代理聲明表示:該代理指向的方法必須是返回空類型,並且擁有兩個參數,第一個是字符串類型,第二個是整型。
2.代理“實例化”:
代理聲明相當於類的定義。有了類的定義后我們要還需生成這個類的對象;同樣有了代理的聲明我們還需要“實例化”代理
如:MyDelegate md = new MyDelegate(Show);
這里的md就是代理變量。在代理的“實例化”的時候必須在構造函數中傳入一個方法名。這個方法名就是該代理指向的方法,當然該方法的返回值類型與參數類型一定要與代理的聲明一致。
Show方法定義如下:
public static void Show(string str, int index)
{
Console.WriteLine("Show"+str+index.ToString());
}
3.代理的調用:
md("hello world",22);
此時調用的就是md這個代理變量所指向的Show方法。
4.例子:
delegate void MyDelegate(string str,int index);
class Test
{
public static void Show(string str, int index)
{
Console.WriteLine("Show"+str+index.ToString());
}
public static void Main(string[] args)
{
MyDelegate md = new MyDelegate(Show);
md("hello world",22);
}
}
5.代理的應用:
代理的主要應用就是在DotNet中的事件處理,所以要想研究事件我們必須要理解代理的概念。有的文章使用代理進行冒泡排序,我感覺這沒必要,因為不用代理我也可以排序,更況且在C#語法中也不需要我們手動編寫冒泡排序代碼。
關於代理,大家要理解代理是個什么東西,並且能夠寫一個簡單的代理示例就可以了。
二、多播代理
上面我們講的代理是一個代理對象指向一個方法,在調用該代理對象的時候就會調用它所指向的方法。多播代理就是為一個代理掛接上多個方法,當執行該代理的時候就會依次執行該代理上掛接的方法。
1.多播代理的聲明與上面講得基本上一樣:
[public/private] delegate void <代理名稱>(<參數列表>);
只有一點不一樣的就是,多播代理所指向的方法應當是void類型。
2.多播代理“實例化”
多播代理“實例化”與上面講得一樣,在此不多說了。
如:MyDelegate md = new MyDelegate(Show);
3.多播代理掛接多個方法。
多播代理可以使用 += 運算符掛接多個方法,也可以使用 -= 運算符從掛接列表中刪除相應的掛接方法。
如:
delegate void MyDelegate(string str,int index);
class Test
{
public static void Show(string str, int index)
{
Console.WriteLine("Show"+str+index.ToString());
}
public static void TestInt(string str, int index)
{
Console.WriteLine("Testint");
}
public static void Main2(string[] args)
{
MyDelegate md = new MyDelegate(Show);
md += new MyDelegate(TestInt);
md("hello world",22);
}
}
在上面這個例子當中有兩個方法(Show和TestInt)符合MyDelegate代理的簽名,如果要把這兩個方法掛接到我們一個代理變量上去的話,就得用 += 運算符了。
MyDelegate md = new MyDelegate(Show);
md += new MyDelegate(TestInt);
這里的md代理變量上先掛接了Show方法,再掛接TestInt方法。當執行md("hello world",22)的時候會先調用Show方法,再調用TestInt方法。
事件本身就是一種多播代理
三、事件
C#中的事件就是代理的一個變量。它和屬性、方法一樣,都是類的成員。只不過事件是指向一個方法,當事件被觸發時,就會執行對象的相關方法。
事件的這種對方法的引用並不是寫死在代碼里面的,而是可以進行更改的。辟如:我們在DotNet中按鈕的OnClick事件,它可以指向符合OnClick事件簽名的任何一個方法。
1.事件的定義使用event關鍵字:
public event CryHandler DuckCryEvent;
其中的CryHandler是一個delegate。從上面的代碼我們可以看出來:事件就是一個代理類型的變量。
private delegate void CryHandler();
2.指定事件處理程序:
指定事件處理程序就是為事件掛接方法的過程。
DuckCryEvent +=new CryHandler(Cry);
public void Cry()
{
Console.WriteLine("我是一只小鴨,呀依呀依呀....");
}
3.執行事件
執行事件就是調用事件所指向方法的過程。一般對事的執行代碼寫在相應的方法或屬性中,如果方法或屬性被調用時就觸發事件。
public void BeShaked()
{
DuckCryEvent();
}
4.完整的例子:
//事件用到的代理,以般以×××Handler的格式進行命名
private delegate void CryHandler();
//玩具小鴨的類
class Duck
{
//定義小鴨的唱歌事件
public event CryHandler DuckCryEvent;
public Duck()
{
//把小鴨唱歌的事件掛接到Cry方法上
DuckCryEvent +=new CryHandler(Cry);
}
//小鴨唱歌事件對應的處理方法
public void Cry()
{
Console.WriteLine("我是一只小鴨,呀呀呀....");
}
//小鴨被搖動
public void BeShaked()
{
//執行事件
DuckCryEvent();
}
}
class Class2
{
public static void Main3(string[] args)
{
//買一只小鴨
Duck d = new Duck();
//搖一搖小鴨,它就會調觸發小鴨的Cry事件,小鴨就會唱歌
d.BeShaked();
}
}
四、事件在ASP.NET中的舉例
頁面源視圖代碼
<form id="form1" runat="server">
<asp:Button ID="Button1" runat="server" onclick="Haha" Text="Button" />
</form>
這個按鈕默認執行的是頁面對象HaHa方法。
頁面后置代碼
protected void XiXi(object sender, EventArgs e)
{
Response.Write("XiXi<br>");
}
protected void Button1_Click(object sender, EventArgs e)
{
Response.Write("Button1Click<br>");
}
protected void Haha(object sender, EventArgs e)
{
Response.Write("HaHa<br>");
}
上面的這三個方法都是符合按鈕的OnClick事件簽名格式
因此我們可以讓按鈕點擊的時候,把這三個方法都執行一遍。實現方式當然就是多播代理了。
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Button1.Click += new EventHandler(Button1_Click);
Button1.Click += new EventHandler(XiXi);
}
五、綜合案例分析
1.面試題:晚上貓大叫一聲,主人被驚醒,老鼠被下跑。用C#實現這個過程。
這個題目中一共有三個對象:貓、主人和老鼠。這三個對象之間即不屬於Is a...的關系也不屬於Has a...的關系,但三者又有相互聯系。
不能把主人和老鼠作為貓的成員變量出現,因為貓對象並不擁有主人和老鼠這兩個對象。
不能在貓任何方法中實例化主人和老鼠。因為貓的任何動作都不會動態產生主人和老鼠。
不能在貓的Cry方法中傳入主人對象和老鼠對象,因為貓大叫不是為了叫醒主人或嚇跑老鼠,可能是貓正在說夢話。所以把主人和老鼠對象傳遞給貓的Cry()方法也不合情理。
通過上面分析我們看到,即不能把主人和老鼠作為貓的成員變量,又不能動態產生或接收主人、老鼠對象,那這三者之間如何建立關系呢?
當然是是我們剛講過的事件。
主人類中有一個“驚醒”的方法;貓的類中有一個“大叫”的方法;老鼠類中有一個“逃跑”方法。並且貓中還有一個“大叫”事件,這個事件是多播代理,它依次調用主人的“驚醒”方法和老鼠的“逃跑”方法。
下面是具體代碼:
//定義貓大叫事件的代理
public delegate void AlertHandler();
//主人類
class Human
{
//主人被驚醒的方法
public void Wake()
{
Console.WriteLine("主人:死貓別叫");
}
}
//老鼠類
class Mouse
{
//老鼠被嚇包的方法
public void Run()
{
Console.WriteLine("老鼠:有危險,快撤!");
}
}
//貓類
class Cat
{
//貓大叫事件
public event AlertHandler AlertEvent;
public Cat()
{
//貓大叫時執行Cry方法
AlertEvent +=new AlertHandler(Cry);
}
//貓大叫事件執行的處理程序
public void Alert()
{
Console.WriteLine("貓:嗚哇...嗚哇...");
}
//貓大叫的方法
public void Cry()
{
//觸發貓大叫的事件
AlertEvent();
}
}
//房子類
class House
{
//房子里有一只老鼠、一只貓和主人
public Mouse mouse = new Mouse();
public Cat cat = new Cat();
public Human human = new Human();
//由於在一個房子里,貓大叫的事件會引發老鼠“逃跑”和主人“驚醒”
//所以在這里把老鼠“逃跑”和主人“驚醒”兩個方法掛接到貓大叫的事件上。
public House()
{
cat.AlertEvent +=new AlertHandler(mouse.Run);
cat.AlertEvent +=new AlertHandler(human.Wake);
}
}
//客戶程序
class Program
{
static void Main(string[] args)
{
//有一間房子
House h = new House();
//貓大叫
h.cat.Cry();
}
}
運行效果:
《圖1》(http://hi.baidu.com/grayworm)
2.鬧鍾:
//鬧鍾事件數據
class TimeArgs : EventArgs
{
//鬧鍾聲明
private string _Message;
//鬧鈴時間
private int _RingTime;
//默認鬧鈴
public TimeArgs()
{
_Message = "滴滴...滴滴...";
_RingTime = 0;
}
public TimeArgs(string message, int ringtime)
{
this._Message = message;
this._RingTime = ringtime;
}
//設置鬧鈴聲音
public string Message
{
get
{
return _Message;
}
set
{
_Message = value;
}
}
//設置鬧鈴時間
public int RingTime
{
get
{
return _RingTime;
}
set
{
_RingTime = value;
}
}
}
//鬧鈴代理
delegate void RingHandler(object sender,TimeArgs e);
class Clock
{
//鬧鈴事件
private event RingHandler RingEvent;
public Clock()
{
//把鬧鈴事件掛接到方法上
RingEvent += new RingHandler(Clock_RingEvent);
}
//鬧鈴事件處理程序
private void Clock_RingEvent(object sender, TimeArgs e)
{
Console.WriteLine(e.Message);
}
//鬧鈴時間監測
private void ReadyToRing(TimeArgs e)
{
while (true)
{
System.Threading.Thread.Sleep(1000);
if (DateTime.Now.Hour == e.RingTime)
{
RingEvent(this, e);
}
}
}
//設置鬧鈴
public void SetTime(TimeArgs e)
{
ReadyToRing(e);
}
}
class Class3
{
public static void Main(string[] args)
{
Clock clock = new Clock();
TimeArgs arg = new TimeArgs("賴蟲起床......", 21);
clock.SetTime(arg);
}
}