C#中的委託和事件學習(續)第1/3頁

來源:互聯網
上載者:User

引言
本文將討論委託和事件一些更為細節的問題,包括一些大家常問到的問題,以及事件訪問器、異常處理、逾時處理和非同步方法呼叫調用等內容。

為什麼要使用事件而不是委託變數?
在 C#中的委託和事件 中,我提出了兩個為什麼在類型中使用事件向外部提供方法註冊,而不是直接使用委託變數的原因。主要是從封裝性和易用性上去考慮,但是還漏掉了一點,事件應該由事件發行者觸發,而不應該由用戶端(客戶程式)來觸發。這句話是什麼意思呢?請看下面的範例:

NOTE:注意這裡術語的變化,當我們單獨談論事件,我們說發行者(publisher)、訂閱者(subscriber)、用戶端(client)。當我們討論Observer模式,我們說主題(subject)和觀察者(observer)。用戶端通常是包含Main()方法的Program類。

class Program {
static void Main(string[] args) {
Publishser pub = new Publishser();
Subscriber sub = new Subscriber();

pub.NumberChanged += new NumberChangedEventHandler(sub.OnNumberChanged);
pub.DoSomething(); // 應該通過DoSomething()來觸發事件
pub.NumberChanged(100); // 但可以被這樣直接調用,對委託變數的不恰當使用
}
}

// 定義委託
public delegate void NumberChangedEventHandler(int count);

// 定義事件發行者
public class Publishser {
private int count;
public NumberChangedEventHandler NumberChanged; // 聲明委託變數
//public event NumberChangedEventHandler NumberChanged; // 聲明一個事件

public void DoSomething() {
// 在這裡完成一些工作 ...

if (NumberChanged != null) { // 觸發事件
count++;
NumberChanged(count);
}
}
}

// 定義事件訂閱者
public class Subscriber {
public void OnNumberChanged(int count) {
Console.WriteLine("Subscriber notified: count = {0}", count);
}
}

上面代碼定義了一個NumberChangedEventHandler委託,然後我們建立了事件的發行者Publisher和訂閱者Subscriber。當使用委託變數時,用戶端可以直接通過委託變數觸發事件,也就是直接調用pub.NumberChanged(100),這將會影響到所有註冊了該委託的訂閱者。而事件的本意應該為在事件發行者在其本身的某個行為中觸發,比如說在方法DoSomething()中滿足某個條件後觸發。通過添加event關鍵字來發布事件,事件發行者的封裝性會更好,事件僅僅是供其他類型訂閱,而用戶端不能直接觸發事件(語句pub.NumberChanged(100)無法通過編譯),事件只能在事件發行者Publisher類的內部觸發(比如在方法pub.DoSomething()中),換言之,就是NumberChanged(100)語句只能在Publisher內部被調用。

大家可以嘗試一下,將委託變數的聲明那行代碼注釋掉,然後取消下面事件聲明的注釋。此時程式是無法編譯的,當你使用了event關鍵字之後,直接在用戶端觸發事件這種行為,也就是直接調用pub.NumberChanged(100),是被禁止的。事件只能通過調用DoSomething()來觸發。這樣才是事件的本意,事件發行者的封裝才會更好。

就好像如果我們要定義一個數字類型,我們會使用int而不是使用object一樣,給予對象過多的能力並不見得是一件好事,應該是越合適越好。儘管直接使用委託變數通常不會有什麼問題,但它給了用戶端不應具有的能力,而使用事件,可以限制這一能力,更精確地對類型進行封裝。

NOTE:這裡還有一個約定俗稱的規定,就是訂閱事件的方法的命名,通常為“On事件名”,比如這裡的OnNumberChanged。

為什麼委託定義的傳回值通常都為void?
儘管並非必需,但是我們發現很多的委託定義傳回值都為void,為什麼呢?這是因為委託變數可以供多個訂閱者註冊,如果定義了傳回值,那麼多個訂閱者的方法都會向發行者返回數值,結果就是後面一個返回的方法值將前面的傳回值覆蓋掉了,因此,實際上只能獲得最後一個方法調用的傳回值。可以運行下面的代碼測試一下。除此以外,發行者和訂閱者是松耦合的,發行者根本不關心誰訂閱了它的事件、為什麼要訂閱,更別說訂閱者的傳回值了,所以返回訂閱者的方法傳回值大多數情況下根本沒有必要。

class Program {
static void Main(string[] args) {
Publishser pub = new Publishser();
Subscriber1 sub1 = new Subscriber1();
Subscriber2 sub2 = new Subscriber2();
Subscriber3 sub3 = new Subscriber3();

pub.NumberChanged += new GeneralEventHandler(sub1.OnNumberChanged);
pub.NumberChanged += new GeneralEventHandler(sub2.OnNumberChanged);
pub.NumberChanged += new GeneralEventHandler(sub3.OnNumberChanged);
pub.DoSomething(); // 觸發事件
}
}

// 定義委託
public delegate string GeneralEventHandler();

// 定義事件發行者
public class Publishser {
public event GeneralEventHandler NumberChanged; // 聲明一個事件
public void DoSomething() {
if (NumberChanged != null) { // 觸發事件
string rtn = NumberChanged();
Console.WriteLine(rtn); // 列印返回的字串,輸出為Subscriber3
}
}
}

// 定義事件訂閱者
public class Subscriber1 {
public string OnNumberChanged() {
return "Subscriber1";
}
}
public class Subscriber2 { /* 略,與上類似,返回Subscriber2*/ }
public class Subscriber3 { /* 略,與上類似,返回Subscriber3*/ }

如果運行這段代碼,得到的輸出是Subscriber3,可以看到,只得到了最後一個註冊方法的傳回值。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.