C#如何正確實(shí)現(xiàn)一個(gè)自定義異常Exception
最近在公司的項(xiàng)目中,編寫了幾個(gè)自定義的 Exception 類。提交 PR 的時(shí)候,sonarqube 提示這幾個(gè)自定義異常不符合 ISerializable patten. 花了點(diǎn)時(shí)間稍微研究了一下,把這個(gè)問題解了。今天在此記錄一下,可能大家都會(huì)幫助到大家。
自定義異常
編寫一個(gè)自定義的異常,繼承自 Exception,其中定義一個(gè) ErrorCode 來存儲(chǔ)異常編號(hào)。平平無(wú)奇的一個(gè)類,太常見了。大家覺得有沒有什么問題?
[Serializable]
public class MyException : Exception
{
public string ErrorCode { get;}
public MyException(string message, string errorCode) : base(message)
{
ErrorCode = errorCode;
}
}如我們對(duì)這個(gè)異常編寫一個(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è)異常,大概是說找不到序列化構(gòu)造器。

Designing Custom Exceptions Guideline
簡(jiǎn)單的搜索了一下,發(fā)現(xiàn)微軟有對(duì)于自定義 Exception 的Designing Custom Exceptions。
總結(jié)一下大概有以下幾點(diǎn):
- 一定要從 System.Exception 或其他常見基本異常之一派生異常。
- 異常類名稱一定要以后綴 Exception 結(jié)尾。
- 應(yīng)使異常可序列化。 異常必須可序列化才能跨越應(yīng)用程序域和遠(yuǎn)程處理邊界正確工作。
- 一定要在所有異常上都提供(至少是這樣)下列常見構(gòu)造函數(shù)。 確保參數(shù)的名稱和類型與在下面的代碼示例中使用的那些相同。
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è)試還是沒通過。獲取 Message 字段的時(shí)候會(huì)直接 throw 一個(gè) Exception。

那么到底該怎么實(shí)現(xiàn)呢?
正確的方式
我們還是按照微軟 guideline 進(jìn)行編寫,但是在序列化構(gòu)造器的上調(diào)用 base 的構(gòu)造器。并且 override 基類的 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)造可以確?;惖?nbsp;Message 字段被正確的還原。這里與其說是序列化構(gòu)造器不如說是反序列化構(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 接口提供的方法,所以基類里肯定有實(shí)現(xiàn)。我們的子類需要 override 它。把自己需要序列化的字段添加到 SerializationInfo 對(duì)象中,這樣在上面反序列化的時(shí)候確保可以把字段的值給恢復(fù)回來。記住不要忘記調(diào)用 base.GetObjectData(info, context), 確保基類的字段數(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è)試,這次順利的通過了,說明 Message 跟 ErrorCode 字段在反序列化后成功的被恢復(fù)了。

總結(jié)
自定義異常是大家日常編碼過程中非常常見的操作。但是看來要寫好一個(gè)自定義異常類也不是那么簡(jiǎn)單。總結(jié)一下需要注意以下幾點(diǎn):
- 添加 [Serializable] Attribute
- 遵守微軟的 guideline,特別是構(gòu)造器部分 Designing Custom Exceptions Guideline
- 在序列化構(gòu)造器對(duì)字段值進(jìn)行恢復(fù),不要忘記調(diào)用基類的序列化構(gòu)造器
- 重寫
GetObjectData方法,把需要序列化的字段添加到SerializationInfo對(duì)象上,同樣不要忘記調(diào)用基類的GetObjectData這個(gè)問題雖然在自定義 Exception 上暴露出來,其實(shí)可以推廣到所有實(shí)現(xiàn)ISerializable接口的類都需要注意 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#開發(fā)簡(jiǎn)易winform計(jì)算器程序
這篇文章主要為大家詳細(xì)介紹了C#開發(fā)簡(jiǎn)易winform計(jì)算器程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
C# MeasureString測(cè)量字符串函數(shù)的使用方法
這篇文章主要介紹了C# MeasureString測(cè)量字符串函數(shù)的使用方法,需要的朋友可以參考下2014-10-10
C#通過chrome插件將HTML網(wǎng)頁(yè)轉(zhuǎn)換為PDF
這篇文章主要介紹了C#通過chrome插件將HTML網(wǎng)頁(yè)轉(zhuǎn)換為PDF,將HTML網(wǎng)頁(yè)內(nèi)容轉(zhuǎn)換為 PDF 格式能方便文檔的后續(xù)打印、存檔和分享等,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2025-03-03
C#在Unity游戲開發(fā)中進(jìn)行多線程編程的方法
這篇文章主要介紹了C#在Unity游戲開發(fā)中進(jìn)行多線程編程的方法,文中總結(jié)了Unity中使用多線程的幾種方式以及一款多線程插件的介紹,需要的朋友可以參考下2016-04-04
C#讀取txt文件數(shù)據(jù)的方法實(shí)例
讀取txt文本數(shù)據(jù)的內(nèi)容,是我們開發(fā)中經(jīng)常會(huì)遇到的一個(gè)功能,這篇文章主要給大家介紹了關(guān)于C#讀取txt文件數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2021-05-05
c# 獲得當(dāng)前絕對(duì)路徑的方法(超簡(jiǎn)單)
下面小編就為大家分享一篇c# 獲得當(dāng)前絕對(duì)路徑的方法(超簡(jiǎn)單),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01

