C#如何正確實(shí)現(xiàn)一個(gè)自定義異常Exception
最近在公司的項(xiàng)目中,編寫(xiě)了幾個(gè)自定義的 Exception 類(lèi)。提交 PR 的時(shí)候,sonarqube 提示這幾個(gè)自定義異常不符合 ISerializable patten. 花了點(diǎn)時(shí)間稍微研究了一下,把這個(gè)問(wèn)題解了。今天在此記錄一下,可能大家都會(huì)幫助到大家。
自定義異常
編寫(xiě)一個(gè)自定義的異常,繼承自 Exception,其中定義一個(gè) ErrorCode
來(lái)存儲(chǔ)異常編號(hào)。平平無(wú)奇的一個(gè)類(lèi),太常見(jiàn)了。大家覺(jué)得有沒(méi)有什么問(wèn)題?
[Serializable] public class MyException : Exception { public string ErrorCode { get;} public MyException(string message, string errorCode) : base(message) { ErrorCode = errorCode; } }
如我們對(duì)這個(gè)異常編寫(xiě)一個(gè)簡(jiǎn)單的單元測(cè)試。步驟如下:
[TestMethod()] public void MyExceptionTest() { // arrange var orignalException = new MyException("Hi", "1000"); var bf = new BinaryFormatter(); var ms = new MemoryStream(); // act bf.Serialize(ms, orignalException); ms.Seek(0, 0); var newException = bf.Deserialize(ms) as MyException; // assert Assert.AreEqual(orignalException.Message, newException.Message); Assert.AreEqual(orignalException.ErrorCode, newException.ErrorCode); }
這個(gè)測(cè)試主要是對(duì)一個(gè) MyException 的實(shí)例使用 BinaryFormatter
進(jìn)行序列化,然后反序列化成一個(gè)新的對(duì)象。將新舊兩個(gè)對(duì)象的 ErrorCode
跟 Message
字段進(jìn)行斷言,也很簡(jiǎn)單。
讓我們運(yùn)行一下這個(gè)測(cè)試,很可惜失敗了。測(cè)試用例直接拋了一個(gè)異常,大概是說(shuō)找不到序列化構(gòu)造器。
Designing Custom Exceptions Guideline
簡(jiǎn)單的搜索了一下,發(fā)現(xiàn)微軟有對(duì)于自定義 Exception 的Designing Custom Exceptions。
總結(jié)一下大概有以下幾點(diǎn):
- 一定要從 System.Exception 或其他常見(jiàn)基本異常之一派生異常。
- 異常類(lèi)名稱一定要以后綴 Exception 結(jié)尾。
- 應(yīng)使異??尚蛄谢?。 異常必須可序列化才能跨越應(yīng)用程序域和遠(yuǎn)程處理邊界正確工作。
- 一定要在所有異常上都提供(至少是這樣)下列常見(jiàn)構(gòu)造函數(shù)。 確保參數(shù)的名稱和類(lèi)型與在下面的代碼示例中使用的那些相同。
public class NewException : BaseException, ISerializable { public NewException() { // Add implementation. } public NewException(string message) { // Add implementation. } public NewException(string message, Exception inner) { // Add implementation. } // This constructor is needed for serialization. protected NewException(SerializationInfo info, StreamingContext context) { // Add implementation. } }
按照上面的 guideline 重新改一下我們的 MyException,主要是添加了幾個(gè)構(gòu)造器。修改后的代碼如下:
[Serializable] public class MyException : Exception { public string ErrorCode { get; } public MyException() { } public MyException(string message, string errorCode) : base(message) { ErrorCode = errorCode; } public MyException(string message, Exception inner): base(message, inner) { } protected MyException(SerializationInfo info, StreamingContext context) { } }
很可惜按照微軟的 guideline 單元測(cè)試還是沒(méi)通過(guò)。獲取 Message
字段的時(shí)候會(huì)直接 throw 一個(gè) Exception。
那么到底該怎么實(shí)現(xiàn)呢?
正確的方式
我們還是按照微軟 guideline 進(jìn)行編寫(xiě),但是在序列化構(gòu)造器的上調(diào)用 base
的構(gòu)造器。并且 override
基類(lèi)的 GetObjectData
方法。
[Serializable] public class MyException : Exception { public string ErrorCode { get; } public MyException() { } public MyException(string message, string errorCode) : base(message) { ErrorCode = errorCode; } public MyException(string message, Exception inner): base(message, inner) { } protected MyException(SerializationInfo info, StreamingContext context): base(info, context) { // Set the ErrorCode value from info dictionary. ErrorCode = info.GetString("ErrorCode"); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (!string.IsNullOrEmpty(ErrorCode)) { // Add the ErrorCode to the SerializationInfo dictionary. info.AddValue("ErrorCode", ErrorCode); } base.GetObjectData(info, context); } }
在序列化構(gòu)造器里從 SerializationInfo
對(duì)象里恢復(fù) ErrorCode
的值。調(diào)用 base 的構(gòu)造可以確保基類(lèi)的 Message
字段被正確的還原。這里與其說(shuō)是序列化構(gòu)造器不如說(shuō)是反序列化構(gòu)造器,因?yàn)檫@個(gè)構(gòu)造器會(huì)在反序列化恢復(fù)成對(duì)象的時(shí)候被調(diào)用。
protected MyException(SerializationInfo info, StreamingContext context): base(info, context) { // Set the ErrorCode value from info dictionary. ErrorCode = info.GetString("ErrorCode"); }
這個(gè) GetObjectData
方法是 ISerializable
接口提供的方法,所以基類(lèi)里肯定有實(shí)現(xiàn)。我們的子類(lèi)需要 override
它。把自己需要序列化的字段添加到 SerializationInfo
對(duì)象中,這樣在上面反序列化的時(shí)候確保可以把字段的值給恢復(fù)回來(lái)。記住不要忘記調(diào)用 base.GetObjectData(info, context)
, 確?;?lèi)的字段數(shù)據(jù)能正確的被序列化。
public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (!string.IsNullOrEmpty(ErrorCode)) { // Add the ErrorCode to the SerializationInfo dictionary. info.AddValue("ErrorCode", ErrorCode); } base.GetObjectData(info, context); }
再次運(yùn)行單元測(cè)試,這次順利的通過(guò)了,說(shuō)明 Message
跟 ErrorCode
字段在反序列化后成功的被恢復(fù)了。
總結(jié)
自定義異常是大家日常編碼過(guò)程中非常常見(jiàn)的操作。但是看來(lái)要寫(xiě)好一個(gè)自定義異常類(lèi)也不是那么簡(jiǎn)單??偨Y(jié)一下需要注意以下幾點(diǎn):
- 添加 [Serializable] Attribute
- 遵守微軟的 guideline,特別是構(gòu)造器部分 Designing Custom Exceptions Guideline
- 在序列化構(gòu)造器對(duì)字段值進(jìn)行恢復(fù),不要忘記調(diào)用基類(lèi)的序列化構(gòu)造器
- 重寫(xiě)
GetObjectData
方法,把需要序列化的字段添加到SerializationInfo
對(duì)象上,同樣不要忘記調(diào)用基類(lèi)的GetObjectData
這個(gè)問(wèn)題雖然在自定義 Exception 上暴露出來(lái),其實(shí)可以推廣到所有實(shí)現(xiàn)ISerializable
接口的類(lèi)都需要注意 3,4 兩點(diǎn)。
以上就是C#如何正確實(shí)現(xiàn)一個(gè)自定義異常Exception的詳細(xì)內(nèi)容,更多關(guān)于C#自定義異常的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#開(kāi)發(fā)簡(jiǎn)易winform計(jì)算器程序
這篇文章主要為大家詳細(xì)介紹了C#開(kāi)發(fā)簡(jiǎn)易winform計(jì)算器程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02C# MeasureString測(cè)量字符串函數(shù)的使用方法
這篇文章主要介紹了C# MeasureString測(cè)量字符串函數(shù)的使用方法,需要的朋友可以參考下2014-10-10C#通過(guò)chrome插件將HTML網(wǎng)頁(yè)轉(zhuǎn)換為PDF
這篇文章主要介紹了C#通過(guò)chrome插件將HTML網(wǎng)頁(yè)轉(zhuǎn)換為PDF,將HTML網(wǎng)頁(yè)內(nèi)容轉(zhuǎn)換為 PDF 格式能方便文檔的后續(xù)打印、存檔和分享等,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2025-03-03C#在Unity游戲開(kāi)發(fā)中進(jìn)行多線程編程的方法
這篇文章主要介紹了C#在Unity游戲開(kāi)發(fā)中進(jìn)行多線程編程的方法,文中總結(jié)了Unity中使用多線程的幾種方式以及一款多線程插件的介紹,需要的朋友可以參考下2016-04-04C#讀取txt文件數(shù)據(jù)的方法實(shí)例
讀取txt文本數(shù)據(jù)的內(nèi)容,是我們開(kāi)發(fā)中經(jīng)常會(huì)遇到的一個(gè)功能,這篇文章主要給大家介紹了關(guān)于C#讀取txt文件數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2021-05-05c# 獲得當(dāng)前絕對(duì)路徑的方法(超簡(jiǎn)單)
下面小編就為大家分享一篇c# 獲得當(dāng)前絕對(duì)路徑的方法(超簡(jiǎn)單),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01