從前,在南方一塊奇異的土地上,有個工人名叫彼得,他非常勤奮,對他的老闆總是百依百順。但是他的老闆是個吝嗇的人,從不信任別人,堅決要求隨時知道彼得的工作進度,以防止他偷懶。但是彼得又不想讓老闆呆在他的辦公室裡站在背後盯著他,於是就對老闆做出承諾:無論何時,只要我的工作取得了一點進展我都會及時讓你知道。彼得通過周期性地使用“帶類型的引用” (原文為:“ typed reference” 也就是 delegate?? )“回調”他的老闆來實實現他的承諾,如下:
class Worker {
public void Advise(Boss boss) { _boss = boss; }
public void DoWork() {
Console.WriteLine(“ 工作 : 工作開始 ”);
if( _boss != null ) _boss.WorkStarted();
Console.WriteLine(“ 工作 : 工作進行中 ”);
if( _boss != null ) _boss.WorkProgressing();
Console.WriteLine("“ 工作 : 工作完成 ”");
if( _boss != null ) {
int grade = _boss.WorkCompleted();
Console.WriteLine(“ 工人的工作得分= ” + grade);
}
}
private Boss _boss;
}
class Boss {
public void WorkStarted() { /* 老闆不關心。 */ }
public void WorkProgressing() { /* 老闆不關心。 */ }
public int WorkCompleted() {
Console.WriteLine(“ 時間差不多! ”);
return 2; /* 總分為 10 */
}
}
class Universe {
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.Advise(boss);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成 ”);
Console.ReadLine();
}
}
介面
現在,彼得成了一個特殊的人,他不但能容忍吝嗇的老闆,而且和他周圍的宇宙也有了密切的聯絡,以至於他認為宇宙對他的工作進度也感興趣。不幸的是,他必須也給宇宙添加一個特殊的回呼函數 Advise來實現同時向他老闆和宇宙報告工作進度。彼得想要把潛在的通知的列表和這些通知的實現方法分離開來,於是他決定把方法分離為一個介面:
interface IWorkerEvents {
void WorkStarted();
void WorkProgressing();
int WorkCompleted();
}
class Worker {
public void Advise(IWorkerEvents events) { _events = events; }
public void DoWork() {
Console.WriteLine(“ 工作 : 工作開始 ”);
if( _events != null ) _events.WorkStarted();
Console.WriteLine(“ 工作 : 工作進行中 ”);
if(_events != null ) _events.WorkProgressing();
Console.WriteLine("“ 工作 : 工作完成 ”");
if(_events != null ) {
int grade = _events.WorkCompleted();
Console.WriteLine(“ 工人的工作得分= ” + grade);
}
}
private IWorkerEvents _events;
}
class Boss : IWorkerEvents {
public void WorkStarted() { /* 老闆不關心。 */ }
public void WorkProgressing() { /* 老闆不關心。 */ }
public int WorkCompleted() {
Console.WriteLine(“ 時間差不多! ”);
return 3; /* 總分為 10 */
}
}
委託
不幸的是,每當彼得忙於通過介面的實現和老闆交流時,就沒有機會及時通知宇宙了。至少他應該忽略身在遠方的老闆的引用,好讓其他實現了 IWorkerEvents的對象得到他的工作報告。( ”At least he'd abstracted the reference of his boss far away from him so that others who implemented the IWorkerEvents interface could be notified of his work progress” 原話如此,不理解到底是什麼意思 )
他的老闆還是抱怨得很厲害。 “彼得! ”他老闆吼道, “你為什麼在工作一開始和工作進行中都來煩我?!我不關心這些事件。你不但強迫我實現了這些方法,而且還在浪費我寶貴的工作時間來處理你的事件,特別是當我外出的時候更是如此!你能不能不再來煩我? ”
於是,彼得意識到介面雖然在很多情況都很有用,但是當用作事件時, “粒度 ”不夠好。他希望能夠僅在別人想要時才通知他們,於是他決定把介面的方法分離為單獨的委託,每個委託都像一個小的介面方法:
delegate void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted();
class Worker {
public void DoWork() {
Console.WriteLine(“ 工作 : 工作開始 ”);
if( started != null ) started();
Console.WriteLine(“ 工作 : 工作進行中 ”);
if( progressing != null ) progressing();
Console.WriteLine("“ 工作 : 工作完成 ”");
if( completed != null ) {
int grade = completed();
Console.WriteLine(“ 工人的工作得分= ” + grade);
}
}
public WorkStarted started;
public WorkProgressing progressing;
public WorkCompleted completed;
}
class Boss {
public int WorkCompleted() {
Console.WriteLine("Better...");
return 4; /* 總分為 10 */
}
}
class Universe {
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成 ”);
Console.ReadLine();
}
}
靜態監聽者
這樣,彼得不會再拿他老闆不想要的事件來煩他老闆了,但是他還沒有把宇宙放到他的監聽者列表中。因為宇宙是個包涵一切的實體,看來不適合使用執行個體方法的委託(想像一下,執行個體化一個 “宇宙 ”要花費多少資源 …..),於是彼得就需要能夠對靜態委託進行掛鈎,委託對這一點支援得很好:
class Universe {
static void WorkerStartedWork() {
Console.WriteLine("Universe notices worker starting work");
}
static int WorkerCompletedWork() {
Console.WriteLine("Universe pleased with worker's work");
return 7;
}
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);
peter.started = new WorkStarted(Universe.WorkerStartedWork);
peter.completed = new WorkCompleted(Universe.WorkerCompletedWork);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成 ”);
Console.ReadLine();
}
}
事件
不幸的是,宇宙太忙了,也不習慣時刻關注它裡面的個體,它可以用自己的委託替換了彼得老闆的委託。這是把彼得的 Worker類的的委託欄位做成 public的一個無意識的副作用。同樣,如果彼得的老闆不耐煩了,也可以決定自己來激發彼得的委託(真是一個粗魯的老闆):
// Peter's boss taking matters into his own hands
if( peter.completed != null ) peter.completed();
彼得不想讓這些事發生,他意識到需要給每個委託提供 “註冊 ”和 “反註冊 ”功能,這樣監聽者就可以自己添加和移除委託,但同時又不能清空整個列表也不能隨意激發彼得的事件了。彼得並沒有來自己實現這些功能,相反,他使用了 event關鍵字讓 C#編譯器為他構建這些方法:
class Worker {
...
public event WorkStarted started;
public event WorkProgressing progressing;
public event WorkCompleted completed;
}
彼得知道 event關鍵字在委託的外邊封裝了一個 property,僅讓 C#客戶通過 += 和 -=操作符來添加和移除,強迫他的老闆和宇宙正確地使用事件。
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed += new WorkCompleted(boss.WorkCompleted);
peter.started += new WorkStarted(Universe.WorkerStartedWork);
peter.completed += new WorkCompleted(Universe.WorkerCompletedWork);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成 ”);
Console.ReadLine();
}
“ 收穫 ”所有結果
到這時,彼得終於可以送一口氣了,他成功地滿足了所有監聽者的需求,同時避免了與特定實現的緊耦合。但是他注意到他的老闆和宇宙都為它的工作打了分,但是他僅僅接收了一個分數。面對多個監聽者,他想要 “收穫 ”所有的結果,於是他深入到代理裡面,輪詢監聽者列表,手工一個個調用:
public void DoWork() {
...
Console.WriteLine("“ 工作 : 工作完成 ”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
int grade = wc();
Console.WriteLine(“ 工人的工作得分= ” + grade);
}
}
}
非同步通知:激發 & 忘掉
同時,他的老闆和宇宙還要忙於處理其他事情,也就是說他們給彼得打分所花費的事件變得非常長:
class Boss {
public int WorkCompleted() {
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Better..."); return 6; /* 總分為 10 */
}
}
class Universe {
static int WorkerCompletedWork() {
System.Threading.Thread.Sleep(4000);
Console.WriteLine("Universe is pleased with worker's work");
return 7;
}
...
}
很不幸,彼得每次通知一個監聽者後必須等待它給自己打分,現在這些通知花費了他太多的工作事件。於是他決定忘掉分數,僅僅非同步激發事件:
public void DoWork() {
...
Console.WriteLine("“ 工作 : 工作完成 ”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() )
{
wc.BeginInvoke(null, null);
}
}
}
非同步通知:輪詢
這使得彼得可以通知他的監聽者,然後立即返回工作,讓進程的線程池來調用這些代理。隨著時間的過去,彼得發現他丟失了他工作的反饋,他知道聽取別人的讚揚和努力工作一樣重要,於是他非同步激發事件,但是周期性地輪詢,取得可用的分數。
public void DoWork() {
...
Console.WriteLine("“ 工作 : 工作完成 ”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
IAsyncResult res = wc.BeginInvoke(null, null);
while( !res.IsCompleted ) System.Threading.Thread.Sleep(1);
int grade = wc.EndInvoke(res);
Console.WriteLine(“ 工人的工作得分= ” + grade);
}
}
}
非同步通知:委託
不幸地,彼得有回到了一開始就想避免的情況中來,比如,老闆站在背後盯著他工作。於是,他決定使用自己的委託作為他調用的非同步委託完成的通知,讓他自己立即回到工作,但是仍可以在別人給他的工作打分後得到通知:
public void DoWork() {
...
Console.WriteLine("“ 工作 : 工作完成 ”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
wc.BeginInvoke(new AsyncCallback(WorkGraded), wc);
}
}
}
private void WorkGraded(IAsyncResult res) {
WorkCompleted wc = (WorkCompleted)res.AsyncState;
int grade = wc.EndInvoke(res);
Console.WriteLine(“ 工人的工作得分= ” + grade);
}
宇宙中的幸福
彼得、他的老闆和宇宙最終都滿足了。彼得的老闆和宇宙可以收到他們感興趣的事件通知,減少了實現的負擔和非必需的往返 “差旅費 ”。彼得可以通知他們,而不管他們要花多長時間來從目的方法中返回,同時又可以非同步地得到他的結果。彼得知道,這並不 *十分 *簡單,因為當他非同步激發事件時,方法要在另外一個線程中執行,彼得的目的方法完成的通知也是一樣的道理。但是,邁克和彼得是好朋友,他很熟悉線程的事情,可以在這個領域提供指導。
他們永遠幸福地生活下去 ……<完 >