2009-07-12 25 views
6

Tôi có kịch bản sau đây. Mã máy khách chỉ có quyền truy cập vào FooHandler, không trực tiếp đến các cá thể Foo.cách tốt nhất để thực hiện các sự kiện xích trong C#

public delegate void FooLoaded(object sender, EventArgs e); 

class Foo { 
    public event FooLoaded loaded; 
    /* ... some code ... */ 
    public void Load() { load_asynchronously(); } 
    public void callMeWhenLoadingIsDone() { loaded(this,EventArgs.Empty); } 
} 

class FooHandler { 
    public event FooLoaded OneFooLoaded; 

    /* ... some code ... */ 

    public void LoadAllFoos() { 
     foreach (Foo f in FooList) { 
      f.loaded += new FooLoaded(foo_loaded); 
      f.Load(); 
     } 
    } 

    void foo_loaded(object sender, EventArgs e) { 
     OneFooLoaded(this, e); 
    } 

} 

Sau đó, khách hàng sẽ sử dụng sự kiện OneFooLoaded của lớp FooHandler để nhận thông báo tải foos. Đây có phải là 'sự kiện chuỗi' điều đúng đắn phải làm không? Có lựa chọn thay thế nào không? Tôi không thích điều này (nó cảm thấy sai, tôi không thể diễn tả chính xác tại sao), nhưng nếu tôi muốn người xử lý là điểm truy cập tôi dường như không có nhiều lựa chọn thay thế.

Trả lời

4

Nếu bạn cảm thấy sai vì các sự kiện phức tạp và hướng ra ngoài hơn là cần thiết cho giao tiếp nội bộ của bạn (điều mà tôi tin là ít nhất là một phần sự thật xem xét sự kiện có thể gọi nhiều khách hàng trong khi bạn biết bạn chỉ cần thông báo cho họ, phải không?), sau đó tôi đề xuất phương án thay thế sau. Thay vì sử dụng các sự kiện để giao tiếp hoàn thành Foo cho FooHandler, vì Foo vẫn ở bên trong, bạn có thể thêm tham số gọi lại cho hàm tạo hoặc phương thức tải của Foo, mà Foo có thể gọi khi tải xong. Tham số này có thể chỉ là một hàm nếu bạn chỉ có một cuộc gọi lại, hoặc nó có thể là một giao diện nếu bạn có nhiều. Dưới đây là cách tôi nghĩ mã của bạn sẽ trông giống như giao diện nội bộ được đơn giản hóa:

public delegate void FooLoaded(FooHandler sender, EventArgs e); 

class Foo 
{ 
    Action<Foo> callback; 
    /* ... some code ... */ 
    public void Load(Action<Foo> callback) { this.callback = callback; load_asynchronously(); } 
    public void callMeWhenLoadingIsDone() { callback(this); } 
} 

class FooHandler 
{ 
    public event FooLoaded OneFooLoaded; 

    /* ... some code ... */ 

    public void LoadAllFoos() 
    { 
    foreach (Foo f in FooList) 
    { 
     f.Load(foo_loaded); 
    } 
    } 

    void foo_loaded(Foo foo) 
    { 
    // Create EventArgs based on values from foo if necessary 
    OneFooLoaded(this, null); 
    } 

} 

Lưu ý rằng điều này cũng cho phép bạn được nhập mạnh hơn với đại biểu FooLoaded.

Nếu, mặt khác, nó cảm thấy sai vì sự kiện không cần phải đi qua FooHandler để có được cho khách hàng, sau đó 1) tôi sẽ tranh chấp rằng bởi vì nếu khách hàng không muốn đối phó với Nếu bạn thực sự muốn làm điều đó, bạn có thể thực hiện một số giao diện gọi lại công khai trên Foo mặc dù Foo là riêng tư, hoặc sử dụng một cơ chế như Pavel đề nghị. Tuy nhiên, tôi nghĩ rằng các máy khách thích sự đơn giản của việc thực hiện các trình xử lý sự kiện ít hơn và phân biệt nguồn trong một trình xử lý thay vì phải kết nối (và có khả năng ngắt kết nối) các sự kiện từ hàng tá đối tượng nhỏ hơn.

+0

Tôi ước gì tôi đã thấy điều này trước khi bắt đầu dự án tôi hiện đang làm việc, đã làm cho mọi thứ trở nên đơn giản hơn nhiều đối với tôi (và dễ dàng hơn nhiều đối với việc kiểm tra đơn vị). – ForbesLindesay

1

Tôi có thể nói với bạn rằng loại thác sự kiện này là thứ tôi đã đến khá tự nhiên trong một vài trường hợp và tôi chưa gặp phải vấn đề nghiêm trọng nào với chúng.

Mặc dù tôi không nghĩ rằng mình từng vượt qua các sự kiện một cách minh bạch, nhưng luôn luôn có sự thay đổi ngữ nghĩa. Ví dụ: FooLoaded sẽ trở thành AllFoosLoaded. Nếu bạn muốn áp đặt thay đổi ngữ nghĩa như vậy chỉ vì lợi ích của nó, bạn có thể thay đổi OneFooLoaded thành chỉ số phần trăm (có phải lớp nhận biết cần biết số lượng Foo s được tải không?), Chẳng hạn.

Tôi nghĩ rằng các công trình xây dựng như thế này cảm thấy sai bởi vì event có nghĩa là để phát sóng. Nó không thực sự áp đặt một hợp đồng trên lớp mà phát sóng nó, cũng không áp đặt một hợp đồng trên lớp đăng ký với nó. Tuy nhiên,

Các lớp mặt tiền và nguyên tắc chung về ẩn thông tin được thiết kế để tạo thuận lợi cho việc thực thi hợp đồng.

Tôi vẫn đang thu thập suy nghĩ của mình về vấn đề này, xin lỗi nếu ở trên có một chút không rõ ràng, nhưng tôi không biết nếu có cách nào tốt hơn để làm những gì bạn muốn. Và nếu có, tôi thích xem nó như bạn.

1

Bạn có thể ủy addremove về sự kiện này, thay vì tăng lương:

class FooHandler { 
    public event FooLoaded OneFooLoaded { 
     add { 
      foreach (Foo f in FooList) { 
       f.loaded += new FooLoaded(value); 
      } 
     } 
     remove { 
      foreach (Foo f in FooList) { 
       f.loaded -= new FooLoaded(value); 
      } 
     } 
    } 

    public void LoadAllFoos() { 
     foreach (Foo f in FooList) { 
      f.Load(); 
     } 
    } 
} 

Ở trên giả định rằng FooList là bất di bất dịch cho các đời của FooHandler. Nếu nó có thể thay đổi, thì bạn cũng sẽ phải theo dõi việc thêm/xóa các mục vào nó và thêm/xóa các trình xử lý cho phù hợp.

3

Một cách khác để làm điều đó là tạo một điểm duy nhất (một lớp đơn) trong miền nơi tất cả các sự kiện đều trải qua. Bất kỳ lớp nào sử dụng miền sẽ nối với lớp đó, có danh sách các sự kiện tĩnh và bất kỳ sự kiện lớp bên trong nào trong miền sẽ được lớp này lắng nghe, do đó tránh sự kiện chuỗi ít nhất trong miền.

Tài liệu tham khảo:

+0

Tôi đã tìm thấy điều này và nghĩ rằng nó tuyệt vời, đơn giản hơn nhiều so với cố gắng kết nối 2 hoặc 3 sự kiện và nhận được mì Ý – Calanus

2

Vài mẹo có thể hay không có thể hữu ích ...

Viết tuyên bố sự kiện như thế này:

public event FooLoaded loaded = delegate {}; 

Bằng cách đó bạn có thể an toàn bắn nó ngay cả khi không có khách hàng nào được nhập ngũ.

Về đối tượng của sự kiện chaining được kết, khi bạn có hai sự kiện:

public event EventHandler a = delegate {}; 
public event EventHandler b = delegate {}; 

Bạn có thể muốn bắn của b cũng gây ra việc sa thải một:

b += (s, e) => a(s, e); 

Và sau đó có lẽ bạn hãy xem xét điều đó và nghĩ rằng sẽ ngắn gọn hơn khi nói:

b += a; 

Thật vậy, Resharper thậm chí có thể đề xuất cho bạn! Nhưng nó có nghĩa là một cái gì đó hoàn toàn khác nhau. Nó đính kèm các nội dung hiện tại của a đến b, vì vậy nếu sau này xử lý thêm tranh thủ với a, điều này sẽ không làm cho chúng được gọi khi b được kích hoạt.

Các vấn đề liên quan