2009-05-07 25 views
5

Giả sử tôi có một phương pháp mà thay đổi trạng thái của một đối tượng, và cháy một sự kiện để thông báo cho thính giả của sự thay đổi trạng thái này:Tôi có thể có sự an toàn và sự kiện ngoại lệ mạnh không?

public class Example 
{ 
    public int Counter { get; private set; } 

    public void IncreaseCounter() 
    { 
     this.Counter = this.Counter + 1; 
     OnCounterChanged(EventArgs.Empty); 
    } 

    protected virtual void OnCounterChanged(EventArgs args) 
    { 
     if (CounterChanged != null) 
     CounterChanged(this,args); 
    } 

    public event EventHandler CounterChanged; 
} 

Việc xử lý sự kiện có thể ném một ngoại lệ ngay cả khi IncreaseCounter hoàn thành xuất sắc thay đổi trạng thái. Vì vậy, chúng tôi không có mạnh exception safety đây:

Việc bảo lãnh mạnh: đó là các hoạt động đã hoặc hoàn thành công hoặc ném một ngoại lệ, rời khỏi trạng thái chương trình chính xác như đó là trước khi phẫu thuật bắt đầu.

Có thể có sự an toàn ngoại lệ mạnh khi bạn cần tăng sự kiện không?

Trả lời

3

Để ngăn chặn một ngoại lệ trong một handler từ tuyên truyền đến các máy phát điện sự kiện, câu trả lời là tự gọi từng mục trong Đại biểu MultiCast (tức là trình xử lý sự kiện) bên trong của một thử-catch

Tất cả các trình xử lý sẽ được gọi và ngoại lệ w on't propagate.

public EventHandler<EventArgs> SomeEvent; 

protected void OnSomeEvent(EventArgs args) 
{ 
    var handler = SomeEvent; 
    if (handler != null) 
    { 
     foreach (EventHandler<EventArgs> item in handler.GetInvocationList()) 
     { 
      try 
      { 
       item(this, args); 
      } 
      catch (Exception e) 
      { 
       // handle/report/ignore exception 
      } 
     }     
    } 
} 

Điều gì còn lại để bạn thực hiện logic cho việc cần làm khi một hoặc nhiều người nhận sự kiện ném và những người khác thì không. Catch() có thể bắt một ngoại lệ cụ thể và quay trở lại bất kỳ thay đổi nào nếu đó là điều có ý nghĩa, cho phép người nhận sự kiện báo hiệu nguồn sự kiện xảy ra tình huống ngoại lệ.

Khi những người khác chỉ ra, sử dụng ngoại lệ vì luồng kiểm soát không được khuyến nghị. Nếu nó thực sự là một trường hợp ngoại lệ, thì bằng mọi cách sử dụng một ngoại lệ. Nếu bạn nhận được rất nhiều ngoại lệ, bạn có thể muốn sử dụng một cái gì đó khác.

+0

Nói cách khác, đạt được sự an toàn ngoại lệ mạnh mẽ trong các phương pháp nâng cao sự kiện đi kèm với chi phí phải nuốt/đăng nhập ngoại lệ. Đó gần như luôn luôn là một điều xấu, vì vậy tôi kết luận rằng đạt được an toàn ngoại lệ mạnh mẽ có lẽ không đáng giá. –

0

Sẽ không đơn giản đổi thứ tự của hai dòng trong IncreaseCounter an toàn ngoại lệ thực thi cho bạn?

Hoặc nếu bạn cần để tăng Counter trước khi bạn nâng cao sự kiện:

public void IncreaseCounter() 
{ 
    this.Counter += 1; 

    try 
    { 
     OnCounterChanged(EventArgs.Empty); 
    } 
    catch 
    { 
     this.Counter -= 1; 

     throw; 
    } 
} 

Trong một tình huống mà bạn có thay đổi trạng thái phức tạp hơn (tác dụng phụ) xảy ra, sau đó nó trở nên khá phức tạp hơn (bạn có thể phải sao chép các đối tượng nhất định chẳng hạn), nhưng trong ví dụ này, nó có vẻ khá dễ dàng.

+0

vấn đề duy nhất với điều này là giá trị của bộ đếm sẽ như trước khi nó được tăng lên (mà sẽ không phải là giá trị kỳ vọng cho hầu hết các nhà phát triển) –

+0

@Alan: Vâng, tôi chỉ xem xét điều đó. Xem phiên bản đã chỉnh sửa của tôi. – Noldorin

+0

Ngoài ra, đừng quên tuyên truyền ngoại lệ. Hiện tại, điều này không hoàn tất thành công hoặc gây ra lỗi cho người gọi. – workmad3

2

Các mô hình chung bạn sẽ thấy ô để làm khung là:

public class Example 
{ 
    public int Counter { get; private set; } 

    public void IncreaseCounter() 
    { 
     OnCounterChanging(EventArgs.Empty); 
     this.Counter = this.Counter + 1; 
     OnCounterChanged(EventArgs.Empty); 
    } 

    protected virtual void OnCounterChanged(EventArgs args) 
    { 
     if (CounterChanged != null) 
     CounterChanged(this, args); 
    } 

    protected virtual void OnCounterChanging(EventArgs args) 
    { 
     if (CounterChanging != null) 
     CounterChanging(this, args); 
    } 

    public event EventHandler<EventArgs> CounterChanging; 
    public event EventHandler<EventArgs> CounterChanged; 
} 

Nếu người dùng muốn ném một ngoại lệ để ngăn chặn sự thay đổi của giá trị thì họ nên làm việc đó trong OnCounterChanging() sự kiện thay vì của OnCounterChanged(). Theo định nghĩa của tên (quá khứ, hậu tố của -ed) ngụ ý giá trị đã được thay đổi.

Edit:

Lưu ý rằng bạn thường muốn tránh xa một lượng dư try..catch tắm/cuối cùng khối như xử lý ngoại lệ (bao gồm try..finally) rất tốn kém tùy thuộc vào việc thực hiện ngôn ngữ. tức là mô hình khung xếp chồng win32 hoặc mô hình ngoại lệ PC-ánh xạ đều có ưu điểm và nhược điểm của chúng, tuy nhiên nếu có quá nhiều khung thì cả hai sẽ tốn kém (cả trong không gian hoặc tốc độ thực thi). Chỉ cần một điều khác cần lưu ý khi tạo một khung công tác.

+2

Điều này sẽ không tốt hơn để đảm bảo an toàn ngoại lệ mạnh mẽ hơn là một quy ước nói 'không ném ngoại lệ trong các trình xử lý sự kiện'. Nó không thực sự ngăn chặn một người nào đó phá vỡ hệ thống bằng cách ném ngoại lệ mà họ không nên. Cho dù đó thực sự là vấn đề hay không thì khác nhau :) – workmad3

+0

Ngoại lệ là một cơ chế hữu ích và chi phí của try/catch là tối thiểu khi không có ngoại lệ. Ngoại lệ không bao giờ nên được sử dụng cho dòng điều khiển bình thường, nhưng tôi không nghĩ đó là những gì OP đang yêu cầu. –

+0

try..catch chi phí tối thiểu cho các trường hợp ngoại lệ dựa trên khung xếp chồng khi không có ngoại lệ. Ngoại lệ PC-ánh xạ có một chi phí kích thước liên kết với họ vì vậy số tiền phong phú của các loại xử lý ngoại lệ có thể bắt đầu sưng lên DS của bạn. –

0

Trong trường hợp này, tôi nghĩ bạn nên thực hiện một gợi ý từ Workflow Foundation. Trong phương thức OnCounterChanged, bao quanh cuộc gọi đến đại biểu với một khối try-catch chung. Trong trình xử lý bắt, bạn sẽ phải thực hiện hành động bù trừ hiệu quả là gì - quay trở lại bộ đếm.

1

Vâng, bạn có thể bù đắp nếu nó là rất quan trọng:

public void IncreaseCounter() 
{ 
    int oldCounter = this.Counter; 
    try { 
     this.Counter = this.Counter + 1; 
     OnCounterChanged(EventArgs.Empty); 
    } catch { 
     this.Counter = oldCounter; 
     throw; 
    } 
} 

Rõ ràng đây sẽ là tốt hơn nếu bạn có thể nói chuyện với lĩnh vực này (kể từ khi gán một lĩnh vực sẽ không lỗi).Bạn cũng có thể quấn này trong bolierplate:

void SetField<T>(ref T field, T value, EventHandler handler) { 
    T oldValue = field; 
    try { 
     field = value; 
     if(handler != null) handler(this, EventArgs.Empty); 
    } catch { 
     field = oldField; 
     throw; 
    } 
} 

và gọi:

SetField(ref counter, counter + 1, CounterChanged); 

hoặc một cái gì đó tương tự ...

+0

Tôi đã xem xét giải pháp rollback nhưng nếu có xử lý sự kiện đã xử lý thành công thay đổi, thì họ sẽ phải biết rằng nó đã được khôi phục. Sau đó, tôi sẽ phải nâng cao sự kiện một lần nữa, nhưng ngoại lệ tương tự có thể được ném lại một lần nữa, vv .. –

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