.NET:何時應該 “包裝異常”?


背景

提到異常,我們會想到:拋出異常、異常恢復、資源清理、吞掉異常、重新拋出異常、替換異常、包裝異常。本文想談談 “包裝異常”,主要針對這個問題:何時應該 “包裝異常”?

“包裝異常” 的技術形式

包裝異常是替換異常的特殊形式,具體的技術形式如下:

1             try
2             {
3                 // do something
4             }
5             catch (SomeException ex)
6             {
7                 throw new WrapperException("New Message", ex);
8             }

注意:WrapperException 需要將 ex 作為 InnerException,這樣才不至於丟失 StackTrace,WrapperException.StackTrace 和 ex.StackTrace 共同構成了完整的 StackTrace。

讓例子幫助我們得出答案

有這樣一種場景:我希望為各種 ORM 框架提供一種抽象,這可以讓應用開發人員自由的在不同的 ORM 實現之間做出選擇。

第一個版本的實現

實現偽代碼

 1     interface IRepository<TEntity>
 2     {
 3         void Update(TEntity entity);
 4     }
 5 
 6     class EntityFrameworkRepository<TEntity> : IRepository<TEntity>
 7     {
 8         public void Update(TEntity entity)
 9         {
10             throw new EntityFrameworkConcurrentException(); // 可能會拋出這樣的異常,這里的代碼不是十分准確。
11         }
12     }
13 
14     class NHibernateRepository<TEntity> : IRepository<TEntity>
15     {
16         public void Update(TEntity entity)
17         {
18             throw new NHibernateConcurrentException(); // 可能會拋出這樣的異常,這里的代碼不是十分准確。
19         }
20     }

有什么問題?

處理並發異常是應用層開發人員的一個非常重要的職責,他們或者選擇自動重試、或者選擇讓用戶重試、甚至允許並發帶來的不一致性,如果使用了上面的接口問題就大了,應用中該攔截哪種並發異常呢?EntityFrameworkConcurrentException?NHibernateConcurrentException?這樣的接口和實現無論如何都達不到:OCP 和 LSP。

將異常作為契約的一部分

實現偽代碼

 1     interface IRepository<TEntity>
 2     {
 3         void Update(TEntity entity);
 4     }
 5 
 6     class ConcurrentException : Exception
 7     {
 8     }
 9 
10     class EntityFrameworkRepository<TEntity> : IRepository<TEntity>
11     {
12         public void Update(TEntity entity)
13         {
14             try
15             {
16             }
17             catch (EntityFrameworkConcurrentException ex)
18             {
19                 throw new ConcurrentException(ex);
20             }
21         }
22     }
23 
24     class NHibernateRepository<TEntity> : IRepository<TEntity>
25     {
26         public void Update(TEntity entity)
27         {
28             try
29             {
30             }
31             catch (NHibernateConcurrentException ex)
32             {
33                 throw new ConcurrentException(ex);
34             }
35         }
36     }

有什么問題?

目前來說還覺得不錯,如果 C# 編譯器或 CLR 能支持異常契約就好了,Java 雖然支持,但是對於調用者來說又不太友好。

這里給出答案

當異常是契約的一部分時,才需要包裝異常。

可能還會有其它答案,等我再思考思考,朋友們也可以給出一些想法。

微軟的一個反例

 MethodBae.Invoke

1         //   System.Reflection.TargetInvocationException:
2         //     調用的方法或構造函數引發異常。
3         //
4         //   System.MethodAccessException:
5         //     調用方沒有調用此構造函數的權限。
6         //
7         //   System.InvalidOperationException:
8         //     聲明此方法的類型是開放式泛型類型。 即,System.Type.ContainsGenericParameters 屬性為聲明類型返回 true。
9         public abstract object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);

當方法內部拋出異常時,Invoke 會將內部異常給包裝起來,這明顯不是我們期望的行為,后來微軟的 dynamic 調用 和 CreateDelegate 之后使用 Delegate 調用 都修復了這個問題。

備注

最近在讀第四版的 clr via c#,確實是一部好書,關於異常作為契約部分的想法,和作者產生了很大的共鳴,書中對異常處理的講解非常細致,推薦大家讀一讀這本書。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM