Posts Tagged event
Bàn thêm về event trong C#
Ban đầu tôi không có ý định viết bài này, bởi lẽ chủ đề về event trong C# có thể nói là xưa như trái đất! Nhưng tình cờ tôi gặp phải một câu hỏi khá thú vị từ một người bạn: sử dụng hay không sử dụng từ khóa event có gì khác nhau? Chúng ta hãy xem qua một ví dụ đơn giản sau:
public class Animal
{
public void Sleep()
{
Thread.Sleep(3000);
// raise the event
if(WokenUp != null)
WokenUp(this, null);
}
public event EventHandler WokenUp;
}
public class Farmer
{
public Farmer()
{
Animal a = new Animal();
a.WokenUp += new EventHandler(HandleEvent);
a.Sleep();
}
private void HandleEvent(object sender, EventArgs e)
{
Console.WriteLine("Your animal has woken up. Feed it now!");
}
}
Đây là một ví dụ kinh điển về cách tạo và sử dụng event: lớp Animal khai báo event WokenUp, phương thức xử lý event này có signature được định nghĩa bởi delegate EventHandler. Lớp Farmer sau đó đăng kí event này trong constructor với toán tử +=
a.WokenUp += new EventHandler(HandleEvent);
Ta có đoạn chương trình chính như sau:
public static void Main()
{
Console.WriteLine("Begin execution");
Farmer f = new Farmer();
Console.WriteLine("End execution");
}
Chạy thử chương trình và ta sẽ nhận được kết quả:
Begin execution
Your animal has woken up. Feed it now!
End execution
Cho đến đây có vẻ như mọi chuyện đều bình thường! Tuy nhiên có một câu hỏi phát sinh: từ khóa event mang ý nghĩa gì? Ta hãy thử quay lại dòng code sau và xóa từ khóa event đi:
public event EventHandler WokenUp;
Sau đó biên dịch và thực thi lại chương trình. Kết quả? Mọi thứ vẫn hoạt động bình thường!
Thực ra, kết quả trên không có gì là bất ngờ nếu ta tinh ý hơn một chút. Khi không còn từ khóa event nữa thì dòng code trên mang ý nghĩa khai báo một delegate instance thông thường. Vì vậy, đoạn code dùng để raise event trước đây:
if(WokenUp != null)
WokenUp(this, null);
trở thành đoạn code để invoke delegate. Toán tử += để đăng kí event cũng chính là toán tử để thêm phương thức vào delegate. Vậy có phải từ khóa event chỉ dùng để trang trí?
Hoàn toàn không phải vậy! Mặc dù bề ngoài có vẻ khá giống nhau, từ khóa event mang lại một số khác biệt căn bản. Khác biệt thứ nhất nằm ở tầm vực: khi khai báo một delegate object là public, delegate object này sẽ có tầm vực truy xuất như một biến public, nghĩa là nó hoàn toàn có thể được invoke từ bên ngoài lớp khai báo (lớp Farmer có thể invoke trực tiếp public delegate WokenUp của Animal). Ngược lại, khi khai báo một public event thì chỉ có chính lớp khai báo có quyền raise event đó. Các lớp khác vẫn có thể “thấy” được event này và đăng kí bằng toán tử +=, nhưng quyền raise event bây giờ hoàn toàn phụ thuộc vào lớp khai báo event.
Khác biệt thứ hai nằm ở chỗ: không thể khai báo delegate instance trong một interface, vì delegate instance được coi như một biến instance, và C# không cho phép khai báo biến trong interface. Do đó đoạn code sau:
interface IMyInterface
{
public EventHandler handler;
}
sẽ gây ra lỗi biên dịch. Trong khi đó, ta vẫn có thể khai báo event trong interface và hiện thực event này trong các lớp hiện thực interface đó. Ví dụ:
interface IConnectible
{
event EventHandler Connected;
}
class Client : IConnectible
{
event EventHandler Connected;
public void Connect()
{
Console.WriteLine("Connecting to server...");
if(Connected != null)
Connected(this, null);
}
}
Trường hợp đặc biệt là khi một lớp hiện thực hai interface có hai event cùng tên. Lúc này ta phải hiện thực tường minh các event của từng interface. Điều này đồng nghĩa với việc tự viết hai phương thức add và remove cho mỗi event (hai phương thức này bình thường được compiler tự động thêm vào khi khai báo event):
interface INetworkDevice
{
event EventHandler Connected;
}
class IPCamera : IConnectible, INetworkDevice
{
event EventHandler Connected_1;
event EventHandler Connected_2;
event EventHandler IConnectible.Connected
{
add
{
lock (Connected_1)
{
Connected_1 += value;
}
}
remove
{
lock (Connected_1)
{
Connected_1 -= value;
}
}
}
event EventHandler INetworkDevice.Connected
{
add
{
lock (Connected_2)
{
Connected_2 += value;
}
}
remove
{
lock (Connected_2)
{
Connected_2 -= value;
}
}
}
}
Lưu ý là đoạn code trên mô phỏng cách mà compiler tự động tạo ra hai phương thức add và remove này: compiler thực hiện một cơ chế locking đơn giản để đảm bảo an toàn trong trường hợp ứng dụng đa luồng (dù rằng hiệu quả của cơ chế locking này vẫn còn nhiều ý kiến khác nhau, và thường không có một giải pháp chung cho tất cả các vấn đề đồng bộ hóa trong lập trình đa luồng). Nếu ta không sử dụng đa luồng trong chương trình thì có thể thực hiện một tối ưu nhỏ bằng cách bỏ đoạn code locking này đi.
Hi vọng là bài viết này đã giúp làm rõ được một vài vấn đề với sự kiện trong C#. Cảm ơn mọi ý kiến đóng góp của các bạn!