Chương 22: Sự kiện
Tác giả: Sưu tầm
Khái quát
Một lớp có thể sử dụng sự kiện để báo cho lớp khác (hay nhiều lớp khác) cài gì đó đã xảy ra. Sự kiện sử dụng thuật ngữ “xuất bản – đăng bao”; một lớp xuất bản các sự kiện có thể gọi về, và các lớp quan tâm đến một sự kiện nhất định có thể đăng bao đến sự kiện đó.
Các sự kiện thường được sử dụng trong giao diện đồ hoạ người sử dụng để thông báo người sử dụng đã thực hiện một lựa chọn, nhưng chúng được thoả mãn cho bất kỳ thao tác bất đồng bộ nào, như một tâp tin bị thay đổi, hay một thông điệp email đến.
Thường trình mà một sự kiện sẽ gọi được định nghĩa bằng một thẻ uỷ quyền. Để dễ dàng hơn trong việc xử lý sự kiện, quy ước thiết kế cho các sự kiện đó là thẻ uỷ quyền luôn luôn có hai tham số. Tham số thứ nhất là đối tượng gọi lại sự kiện, và tham số thứ hai là một đối tượng chứa thông tin về sự kiện. Đối tượng này luôn luôn được dẫn xuất từ lớp EventsArgs.
Sự kiện New Email
Đây là một ví dụ về sự kiện.
using System;
class NewEmailEventArgs: EventArgs {
public NewEmailEventArgs(string subject, string message) {
this.subject = subject;
this.message = message;
}
public string Subject {
get {
return(subject);
}
}
public string Message {
get {
return(message);
}
}
string subject;
string message;
}
class EmailNotify {
public delegate void NewMailEventHandler(object sender, NewEmailEventArgs e);
public event NewMailEventHandler OnNewMailHandler;
protected void OnNewMail(NewEmailEventArgs e) {
if (OnNewMailHandler != null)
OnNewMailHandler(this, e);
}
public void NotifyMail(string subject, string message) {
NewEmailEventArgs e = new NewEmailEventArgs(subject, message);
OnNewMail(e);
}
}
class MailWatch {
public MailWatch(EmailNotify emailNotify) {
this.emailNotify = emailNotify;
emailNotify.OnNewMailHandler +=
new EmailNotify.NewMailEventHandler(IHaveMail);
}
void IHaveMail(object sender, NewEmailEventArgs e) {
Console.WriteLine(“New Mail: {0}\n{1]”, e.Subject, e.Message);
}
EmailNotify emailNotify;
}
class Test {
public static void Main() {
EmailNotify emailNotify = new EmailNotify();
MailWatch mailWatch = new MailWatch(emailNotify);
emailNotify.NotifyMail(“Hello!”, “Welcome to Events!!!”);
}
}
Lớp NewEmailEventsArgs chứa thông tin được chuyển khi sự kiện NewEmail được gọi.
Lớp EmailNotify chịu trách nhiệm xử lý sự kiện; nó khai báo một thẻ uỷ quyền định nghĩa các tham số được chuyển khi sự kiện được gọi lên, và nó cũng tự định nghĩa sự kiện. Hàm OnNewMail() được sử dụng để gọi sự kiện, và hàm giúp đỡ NotifyMail() lấy thông tin sự kiện, đóng gói nó trong một thể hiện của NewEmailEventsArgs, và gọi hàm OnNewMail() để phát sự kiện.
Lớp MailWatch là một người tiêu thụ của lớp EmailNotify. Nó tạo một thể hiện của lớp EmailNotify và móc hàm IHaveMail() vào sự kiện OnNewMailHandler. Cuối cùng, hàm Main() tạo các thể hiện của EmailNotify và MailWatch, và sau đó gọi hàm NotifyMail() để phát sự kiện.
Trường sự kiện
Trong ví dụ trước, trường sự kiện là EmailNotify.OnNewMailHandler. Với lớp có chứa trường sự kiện, không có các hạn chế trong cách dùng.
Tuy nhiên, ngoài khai báo của EmailNotify, một trường sự kiện chỉ có thể được sử dụng trên phần bên trái các toán tử += và -=; mặt khác trường không thể được khảo sát hoặc sửa đổi.
Các sự kiện đa chuyển đổi
Sự kiện trong C# là đa chuyển đổi, có nghĩa là việc gọi lại một sự kiện có thể gọi nhiều thẻ uỷ quyền với thông tin sự kiện. Thứ tự các thẻ uỷ quyền được gọi là không được định nghĩa, và nếu một thẻ uỷ quyền ném ra một ngoại lệ, nó có thể dẫn đến các thẻ uỷ quyền khác không được gọi.
Các sự kiện sparse
Hầu hết các lớp sẽ cài đặt sự kiện bằng cách sử dụng các trường sự kiện, như được làm trong ví dụ ngay sau đây. Nếu một lớp cài đặt nhiều sự kiện, nhưng chỉ một phần nhỏ của chúng là có vẻ được sử dụng lúc nào đó, việc dự trữ một trường tách biệt cho mỗi sự kiện có thể là lãng phí không gian. Điều này có thể xảy ra với một điều khiển giao diện người sử dụng hỗ trợ nhiều sự kiện.
Trong tình huống này, một lớp có thể khai báo các thuộc tính sự kiện thay cho các trường sự kiện, và sử dụng một cơ chế riêng cho việc lưu trữ các thẻ uỷ quyền bên dưới. Ví dụ sau sửa lại ví dụ trước, bằng cách sử dụng các thuộc tính sự kiện thay cho các trường sự kiện.
using System;
using System.Collections;
class NewEmailEventArgs: EventArgs {
public NewEmailEventArgs(string subject, string message) {
this.subject = subject;
this.message = message;
}
public string Subject {
get {
return(subject);
}
}
public string Message {
get {
return(message);
}
}
string subject;
string message;
}
class EmailNotify {
public delegate void NewMailEventHandler(object sender, NewEmailEventArgs e);
protected Delegate GetEventHandler(object key) {
return((Delegate) handlers[key]);
}
protected void SetEventHandler(object key, Delegate del) {
handlers.Add(key, del);
}
public event NewMailEventHandler OnNewMailHandler {
get {
return((NewMailEventHandler) GetEventHandler(onNewMailKey));
}
set {
SetEventHandler(onNewMailKey, value);
}
}
public void OnNewMail(NewEmailEventArgs e) {
if (OnNewMailHandler != null)
OnNewMailHandler(this, e);
}
public void NotifyMail(string subject, string message) {
NewEmailEventArgs e = new NewEmailEventArgs(subject, message);
OnNewMail(e);
}
Hashtable handlers = new Hashtable();
// unique key for this event
static readonly object onNewMailKey = new object();
}
class MailWatch {
public MailWatch(EmailNotify emailNotify) {
this.emailNotify = emailNotify;
emailNotify.OnNewMailHandler +=
new EmailNotify.NewMailEventHandler(IHaveMail);
}
void IHaveMail(object sender, NewEmailEventArgs e) {
Console.WriteLine(“New Mail: {0}\n{1]”, e.Subject, e.Message);
}
EmailNotify emailNotify;
}
class Test {
public static void Main() {
EmailNotify emailNotify = new EmailNotify();
MailWatch mailWatch = new MailWatch(emailNotify);
emailNotify.NotifyMail(“Hello!”, “Welcome to Events!!!”);
}
}
Lớp EmailNotify bây giờ có một thuộc tính NewMailEventHandler thay vì có một sự kiện cùng tên. Thuộc tính lưu trữ thẻ uỷ quyền trong một bảng băm thay vì đặt nó trong một trường sự kiện, và sau đó sử dụng đối tượng chỉ đọc tĩnh onNewMailKey để đảm bảo rằng thuộc tính tìm chính xác thẻ uỷ quyền. Bởi vì các tham chiếu đối tượng được đảm bảo bằng hệ thống là độc nhất, nên việc tạo một đối tượng chỉ đọc tĩnh là cách tốt để phát sinh một khoá duy nhất tại thời gian chạy.
Trong tình huống này, sử dụng một bảng băm rõ ràng là một gợi ý thua cuộc, vì nó sự chiếm nhiều không gian hơn ví dụ trước. Cách biểu diễn này là đáng giá hơn khi có một phân cấp của các đối tượng – như các điều khiển dẫn xuất từ lớp Control cơ sở có nhiều sự kiện sparse. Lớp Control cài đặt GetEventHandler() và SetEventHandler(), và tất cả các điều khiển dẫn xuất từ nó có thể sử dụng để lưu trữ các thẻ uỷ quyền của chúng.
Chú ý là điều này chỉ tốt nếu có vẻ có nhiều thể hiện của mỗi điều khiển thể hiện tại một thời điểm. Nếu không, không gian sử dụng bởi khoá chỉ đọc tĩnh sẽ phủ nhận không gian lưu trữ trong đối tượng.