白話C#:委託與事件
我們出去吃飯,總是喜歡去人多生意好的館子,因爲這樣的館子往往味道和服務都比較好,而那些生意冷清的館子往往無人問津。生意好的館子固然有其長 處,但去這樣地方就餐又總是需要先排隊等位置,所以排號是比較流行的方式。當然,如果這家館子的座位充足,就不需要排號,但是上菜又比較慢。無論怎樣,如 果廚房一時半會兒無法做好你的菜,那麼你就只好耐心地等待,在這個時候你可以做自己的事情,跟朋友聊天、玩手機或者看美女,沒有人會傻傻地站在廚房門口目 不轉睛地盯着廚師手中的鍋鏟直到做好自己的菜。
在軟件開發工作中,我們也常常會遇到類似的問題,我們向某個服務器(或類似於服務器的東西)發送一個請求,需要獲取到反饋信息後才能繼續某項工作, 但是如果這項工作不是最重要的或者目前唯一的工作項,我們完全沒有必要讓這個等待過程阻礙整個軟件的使用,如果這樣做了,那將獲得非常糟糕的用戶體驗,就 像我們看到有人一動不動地站在廚房門口等菜一樣。
C#提供了委託機制來實現異步處理,也就是說,你向服務器發送請求以後就可以把精力用在做別的事情上,服務器返回請求後應用程序會自動調用你之前安 排好的方法來處理接下來的工作。換句話說,你點好了菜,接下來就可以和朋友聊天,廚房做好了菜無論是叫號還是由服務員端到你桌上來,反正不用你再操心了。
下面我們先創建一個顧客類:
1: public class Customer
2:
{
3: public int ID { get; private set; }
4:
5: public Customer(int id)
6:
{
7: this .ID = id;
8:
}
9:
}
在這個場景中,我們不需要賦予Customer類太多屬性,我們假設這家餐館採用的方式是叫號而不是由服務員把菜端到每個顧客桌上,所以Customer類只要有個ID號就夠了。
接下來我們來實現這個叫號的方法,雖然很簡單,但是肯定只有不是一家餐館採用這種方式,因此我們把這個方法放在單獨的一個類裏面:
1: public class DelegateDemo
2:
{
3: public static void CallCustomer(Customer customer)
4:
{
5: if (customer != null )
6:
{
7: Console.WriteLine("Customer ID: " + customer.ID);
8:
}
9:
}
10:
}
然後我們來創建最重要的類,餐廳類:
1: public class SampleRestaurant
2:
{
3: public List<Customer> Customers { get; private set; }
4:
5: public SampleRestaurant()
6:
{
7: this .Customers = new List<Customer>();
8:
9: for (int index = 1; index <= 10; index++)
10:
{
11: Customers.Add(new Customer(index));
12:
}
13:
}
14:
15: public delegate void EnumCustomerCallback(Customer customer);
16:
17: public void EnumCustomers(EnumCustomerCallback callback)
18:
{
19: foreach (var customer in Customers)
20:
{
21:
callback(customer);
22:
}
23:
}
24:
}
在這個餐廳類裏面,有一個顧客的集合,表示當前餐廳裏所有的顧客,我們先往這個集合裏面塞10個顧客進去以備待會兒叫號。然後我們在這個類裏面聲明 了一個委託EnumCustomerCallback,並限定了它的函數簽名。要使用這個委託的函數就必須符合該委託的簽名形式。例如,該餐館採用叫號的 方式,也就是說我們要把DelegateDemo.CallCustomers方法傳遞給EnumCustomerCallback委託,因此它倆的函數 簽名是一致的。然後我們還定義了一個EnumCustomers方法,該方法的以EnumCustomerCallback委託爲參數。該方法執行的內容 是挨個遍歷當前所有顧客,至於遍歷到每個顧客是採取什麼樣的方式,叫號還是有服務員端菜呢,取決於傳遞給當前委託的方法來執行。
好,接下來我們來執行這段代碼看看效果:
1: static void Main(string [] args)
2:
{
3: SampleRestaurant restaurant = new SampleRestaurant();
4: SampleRestaurant.EnumCustomerCallback callCustomer = new SampleRestaurant.EnumCustomerCallback(DelegateDemo.CallCustomer);
5:
restaurant.EnumCustomers(callCustomer);
6:
7:
Console.ReadLine();
8:
}
在這段代碼的第4行,我們創建了一個委託實例callCustomers,並把叫號的方法DelegateDemo.CallCustomers傳 遞給它座位該餐館上菜的方式。然後我們再把這個方式傳遞給該餐廳的遍歷顧客的方法,讓它在遍歷到每個顧客的時候採用老闆設定好了的方式去執行。
下面的截圖就是以上代碼執行的結果,喇叭裏挨個叫出顧客的編號,通知他們去取餐:
接下來我們來介紹一下事件機制,之所以把事件放在委託後面講,是因爲事件是在委託的基礎上實現的。這一次我們站在廚師的角度來看待什麼是事件。
作爲一個廚師,炒菜是最基本的工作內容,而對於餐廳的服務員來說,可以拿着點菜單去叫廚師來炒菜,因此這就存在一個觀察者模式,即發佈和訂閱的機 制。服務員發佈說:“顧客要個青椒肉絲”,廚師訂閱說:“行,我馬上炒個青椒肉絲”。定義事件需要注意的是,事件需要兩個參數,一是引發該事件的對象,本 例中是負責點菜的服務員;二是事件消息對象。其中,事件消息對象必須派生自System.EventArgs類。
因此我們先來設計一個點菜單作爲事件消息類:
1: public class OrderEventArgs : EventArgs
2:
{
3: public string DishName { get; set; }
4: public int Amount { get; set; }
5:
}
在這個點菜單中,我們只包含了菜名和數量,因爲對於廚師來說,他只要知道這兩個基本信息就夠了。接下來我們來定義這個廚師類:
1: public class Cook
2:
{
3: public Waitress TheWaitress { get; private set; }
4:
5: public Cook(Waitress waitress)
6:
{
7: this .TheWaitress = waitress;
8: this .TheWaitress.OnOrderHandler += new Waitress.OrderEventHandler(waitress_OnOrderHandler);
9:
}
10:
11: void waitress_OnOrderHandler(object sender, OrderEventArgs e)
12:
{
13: Console.WriteLine("I cook " + e.Amount + " " + e.DishName + "." );
14:
}
15:
}
在廚師類中包含了一個Waitress對象,這表示發佈者,也就是叫廚師炒菜的服務員。第8行,廚師把自己添加進預訂者列表,也就是說在服務員下單子的時候,廚房裏得有現成的廚師才行,如果廚房裏沒有廚師,服務員發佈了點菜事件也沒人接招。
然後我們來看看事件的發佈者,服務員:
1: public class Waitress
2:
{
3: public delegate void OrderEventHandler(object sender, OrderEventArgs e);
4: public event OrderEventHandler OnOrderHandler;
5:
6: public void Order(string dishName, int amount)
7:
{
8: if (String.IsNullOrEmpty(dishName) || amount <= 0)
9:
{
10: return ;
11:
}
12:
13: OrderEventArgs orderEventArgs = new OrderEventArgs { DishName = dishName, Amount = amount };
14:
15: if (OnOrderHandler != null )
16:
{
17: OnOrderHandler(this , orderEventArgs);
18:
}
19:
}
20:
}
在這個服務員類裏面,我們首先定義了一個帶有兩個參數的委託以及一個事件。委託的兩個參數一個是發佈者對象,另一個是事件信息對象。然後,服務員類 還提供了一個點餐方法,點餐方法創建一個事件信息類(點菜單),如果事件的訂閱者不爲空,也就是說廚房裏目前有廚師,那麼服務員就會把這個點菜單發送過 去,而事件的訂閱者會有專門的方法來處理這個事件。
1: static void Main(string [] args)
2:
{
3: Waitress waitress = new Waitress();
4: Cook cook = new Cook(waitress);
5:
6: waitress.Order("QingJiaoRouSi" , 1);
7:
8:
Console.ReadLine();
9:
}