2009-04-07 25 views
52

Tôi đang tìm hiểu về Các sự kiện/Đại biểu trong C#. Tôi có thể hỏi ý kiến ​​của bạn về kiểu đặt tên/mã hóa mà tôi đã chọn (được lấy từ cuốn sách Head First C#) không?Sự kiện - quy ước đặt tên và kiểu

Đang dạy bạn bè về điều này vào ngày mai, và tôi đang cố gắng tìm ra cách giải thích các khái niệm thanh lịch nhất. (Nghĩ cách tốt nhất để hiểu một vấn đề là để thử và dạy nó!)

class Program 
    { 
     static void Main() 
     { 
      // setup the metronome and make sure the EventHandler delegate is ready 
      Metronome metronome = new Metronome(); 

      // wires up the metronome_Tick method to the EventHandler delegate 
      Listener listener = new Listener(metronome); 
      metronome.OnTick(); 
     } 
    } 

public class Metronome 
    { 
     // a delegate 
     // so every time Tick is called, the runtime calls another method 
     // in this case Listener.metronome_Tick 
     public event EventHandler Tick; 

     public void OnTick() 
     { 
      while (true) 
      { 
       Thread.Sleep(2000); 
       // because using EventHandler delegate, need to include the sending object and eventargs 
       // although we are not using them 
       Tick(this, EventArgs.Empty); 
      } 
     } 
    } 

public class Listener 
    { 
     public Listener(Metronome metronome) 
     { 
      metronome.Tick += new EventHandler(metronome_Tick); 
     } 

     private void metronome_Tick(object sender, EventArgs e) 
     { 
      Console.WriteLine("Heard it"); 
     } 
    } 

n.b. Mã được cấu trúc lại từ http://www.codeproject.com/KB/cs/simplesteventexample.aspx

Trả lời

46

Có một vài điểm mà tôi sẽ đề cập đến:

Metronome.OnTick dường như không được đặt tên một cách chính xác. Về mặt ngữ nghĩa, "OnTick" nói với tôi nó sẽ được gọi khi nó "Tick" s, nhưng đó không phải là thực sự những gì đang xảy ra. Tôi sẽ gọi nó là "Go" để thay thế.

Mô hình thường được chấp nhận, tuy nhiên sẽ thực hiện như sau. OnTick là một phương pháp ảo giúp tăng sự kiện. Bằng cách này, bạn có thể ghi đè hành vi mặc định trong các lớp kế thừa một cách dễ dàng và gọi cơ sở để nâng cao sự kiện.

class Metronome 
{ 
    public event EventHandler Tick; 

    protected virtual void OnTick(EventArgs e) 
    { 
     //Raise the Tick event (see below for an explanation of this) 
     var tickEvent = Tick; 
     if(tickEvent != null) 
      tickEvent(this, e); 
    } 

    public void Go() 
    { 
     while(true) 
     { 
      Thread.Sleep(2000); 
      OnTick(EventArgs.Empty); //Raises the Tick event 
     } 
    } 
} 

Ngoài ra, tôi biết đây là một ví dụ đơn giản, nhưng nếu không có người nghe kèm theo, mã của bạn sẽ ném vào Tick(this, EventArgs.Empty). Bạn ít nhất phải bao gồm một người bảo vệ null để kiểm tra xem có người nghe:

if(Tick != null) 
    Tick(this, EventArgs.Empty); 

Tuy nhiên, điều này vẫn còn dễ bị tổn thương trong một môi trường đa luồng nếu người nghe là không đăng ký giữa bảo vệ và sự thỉnh nguyện. Các tốt nhất là để nắm bắt những thính giả hiện đầu tiên và gọi cho họ:

var tickEvent = Tick; 
if(tickEvent != null) 
    tickEvent(this, EventArgs.Empty); 

Tôi biết đây là một câu trả lời cũ, nhưng kể từ khi nó vẫn còn thu thập upvotes, đây là C# 6 cách làm việc. Các "bảo vệ" toàn bộ khái niệm có thể được thay thế bằng một phương pháp gọi có điều kiện và trình biên dịch không thực sự làm the Right Thing (TM) liên quan đến chụp người nghe:

Tick?.Invoke(this, EventArgs.Empty); 
+12

Phương án bảo vệ là thêm "= delegate {};" để khai báo Tick (xem http://stackoverflow.com/questions/231525/raising-c-events-with-an-extension-method-is-it-bad/231536#231536) – Benjol

+0

Lưu ý rằng lỗ hổng không phải là _limited_ để môi trường đa luồng. Có thể (nếu sociopathic) cho một trình xử lý sự kiện để loại bỏ tất cả các trình xử lý khỏi một sự kiện, dẫn đến một sự cố khi trình xử lý hoàn tất và sự kiện gọi cố gắng chạy sự kiện tiếp theo (hiện không tồn tại). –

+0

@GregD: Có cách nào để idiotproof nó như vậy mà mã khách hàng không thể làm điều đó? –

2

Có vẻ tốt, ngoài thực tế là OnTick không tuân theo mô hình yêu cầu sự kiện điển hình. Thông thường, On[EventName] làm tăng sự kiện một lần duy nhất, như

protected virtual void OnTick(EventArgs e) 
{ 
    if(Tick != null) Tick(this, e); 
} 

Hãy cân nhắc tạo phương pháp này, và đổi tên phương pháp hiện tại của bạn "OnTick" thành "StartTick", và thay vì gọi Tick trực tiếp từ StartTick, gọi OnTick(EventArgs.Empty) từ StartTick phương pháp.

4

Một điểm tôi đã tìm thấy sau khi sử dụng các sự kiện trong. Net trong nhiều năm là nhu cầu lặp đi lặp lại để kiểm tra sự kiện cho một trình xử lý null trên mọi yêu cầu. Tôi chưa thấy một đoạn mã trực tiếp nào có thể làm bất cứ điều gì nhưng không gọi sự kiện nếu nó là null.

Những gì tôi đã bắt đầu làm là đặt một trình xử lý giả trên mọi sự kiện tôi tạo để tiết kiệm sự cần thiết phải thực hiện kiểm tra rỗng.

public class Metronome 
{ 
    public event EventHandler Tick =+ (s,e) => {}; 

    protected virtual void OnTick(EventArgs e) 
    { 
     Tick(this, e); // now it's safe to call without the null check. 
    } 
} 
+1

Một điều cần chỉ ra là điều này không làm việc với serialization. Ý tưởng đại biểu trống được thảo luận kỹ lưỡng tại http://stackoverflow.com/questions/9033/hidden-features-of-c/9282#9282 –

+1

Nó thậm chí có thể dễ dàng hơn: 'sự kiện công cộng EventHandler Tick = delegate {};' – Mikhail

56

Microsoft đã thực sự viết một loạt các nguyên tắc đặt tên và đặt nó trong thư viện MSDN. Bạn có thể tìm thấy các bài viết ở đây: Guidelines for Names

Bên cạnh những nguyên tắc viết hoa nói chung, đây là những gì nó có cho 'Sự kiện' trên trang Names of Type Members:

Do sự kiện tên với một động từ hoặc một cụm động từ .

Đặt tên sự kiện theo khái niệm trước và sau, sử dụng và quá khứ hiện tại. Ví dụ: sự kiện đóng được thực hiện trước cửa sổ bị đóng sẽ được gọi là Đóng và một sự kiện được nâng lên sau khi cửa sổ là đóng sẽ được gọi là Đóng.

Không sử dụng tiền tố Trước hoặc Sau hoặc hậu tố để chỉ ra trước và sau sự kiện.

Thực hiện xử lý sự kiện tên (các đại biểu đã sử dụng làm loại sự kiện) với hậu tố EventHandler.

Sử dụng hai tham số có tên người gửi và e trong chữ ký xử lý sự kiện.

Thông số người gửi phải thuộc loại Đối tượng và tham số e phải là một phiên bản hoặc kế thừa từ EventArgs.

Thực hiện các lớp đối số sự kiện tên với hậu tố EventArgs.

12

Tôi sẽ nói hướng dẫn tốt nhất cho các sự kiện nói chung, bao gồm quy ước đặt tên, là here.

Đây là ước tôi đã áp dụng, ngắn gọn:

  • Sự kiện tên thường được chấm dứt với một động từ kết thúc với -ing hoặc -ed (Đóng/Đóng, đang tải/Loaded)
  • Lớp mà tuyên bố sự kiện nên có một bảo vệ ảo Trên [EventName] mà nên được sử dụng bởi phần còn lại của lớp để nâng cao sự kiện. Phương thức này cũng có thể được sử dụng bởi các lớp con để nâng cao sự kiện và cũng bị quá tải để sửa đổi logic tăng sự kiện.
  • Thường có sự nhầm lẫn về việc sử dụng 'Trình xử lý' - để kết hợp, tất cả các đại biểu cần được sửa đổi bằng Trình xử lý, cố gắng tránh gọi các phương thức thực hiện các bộ xử lý '
  • Quy ước đặt tên VS mặc định cho phương pháp triển khai trình xử lý là EventPublisherName_EventName.
2

Trong trường hợp của bạn nó có thể là:

class Metronome { 
    event Action Ticked; 

    internalMethod() { 
    // bla bla 
    Ticked(); 
    } 
} 

Trên sử dụng sampple dưới đây quy ước, tự mô tả;]

Sự kiện nguồn:

class Door { 

    // case1: property change, pattern: xxxChanged 
    public event Action<bool> LockStateChanged; 

    // case2: pure action, pattern: "past verb" 
    public event Action<bool> Opened; 

    internalMethodGeneratingEvents() { 
    // bla bla ... 

    Opened(true); 
    LockStateChanged(false); 
    } 

} 

BTW. từ khóa event là không bắt buộc nhưng cho phép phân biệt 'sự kiện' từ 'callbacks'

Sự kiện nghe:

class AlarmManager { 

    // pattern: NotifyXxx 
    public NotifyLockStateChanged(bool state) { 
    // ... 
    } 

    // pattern: [as above]  
    public NotifyOpened(bool opened) { 
    // OR 
    public NotifyDoorOpened(bool opened) { 
    // ... 
    } 

} 

Và ràng buộc [mã trông con người thân thiện]

door.LockStateChanged += alarmManager.NotifyLockStateChanged; 
door.Moved += alarmManager.NotifyDoorOpened; 

Ngay cả bằng tay gửi các sự kiện là "con người có thể đọc được ".

alarmManager.NotifyDoorOpened(true); 

Đôi khi biểu cảm hơn có thể được "verb + ing"

dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting; 

Cho dù ước bạn chọn, phù hợp với nó.