最近在公司的项目中,编写了几个自定义的 Exception 类。提交 PR 的时候,sonarqube 提示这几个自定义异常不符合 ISerializable patten. 花了点时间稍微研究了一下,把这个问题解了。今天在此记录一下,可能大家都会帮助到大家。

自定义异常

编写一个自定义的异常,继承自 Exception,其中定义一个 ErrorCode 来存储异常编号。平平无奇的一个类,太常见了。大家觉得有没有什么问题?

    [Serializable]
public class MyException : Exception
{
public string ErrorCode { get;} public MyException(string message, string errorCode) : base(message)
{
ErrorCode = errorCode;
}
}

如我们对这个异常编写一个简单的单元测试。步骤如下:

        [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);
}

这个测试主要是对一个 MyException 的实例使用 BinaryFormatter 进行序列化,然后反序列化成一个新的对象。将新旧两个对象的 ErrorCodeMessage 字段进行断言,也很简单。

让我们运行一下这个测试,很可惜失败了。测试用例直接抛了一个异常,大概是说找不到序列化构造器。

Designing Custom Exceptions Guideline

简单的搜索了一下,发现微软有对于自定义 Exception 的

Designing Custom Exceptions

总结一下大概有以下几点:

  • 一定要从 System.Exception 或其他常见基本异常之一派生异常。

  • 异常类名称一定要以后缀 Exception 结尾。

  • 应使异常可序列化。 异常必须可序列化才能跨越应用程序域和远程处理边界正确工作。

  • 一定要在所有异常上都提供(至少是这样)下列常见构造函数。 确保参数的名称和类型与在下面的代码示例中使用的那些相同。

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,主要是添加了几个构造器。修改后的代码如下:

    [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 单元测试还是没通过。获取 Message 字段的时候会直接 throw 一个 Exception。



那么到底该怎么实现呢?

正确的方式

我们还是按照微软 guideline 进行编写,但是在序列化构造器的上调用 base 的构造器。并且 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);
}
}

在序列化构造器里从 SerializationInfo 对象里恢复 ErrorCode 的值。调用 base 的构造可以确保基类的 Message 字段被正确的还原。这里与其说是序列化构造器不如说是反序列化构造器,因为这个构造器会在反序列化恢复成对象的时候被调用。

   protected MyException(SerializationInfo info, StreamingContext context): base(info, context)
{
// Set the ErrorCode value from info dictionary.
ErrorCode = info.GetString("ErrorCode");
}

这个 GetObjectData 方法是 ISerializable 接口提供的方法,所以基类里肯定有实现。我们的子类需要 override 它。把自己需要序列化的字段添加到 SerializationInfo 对象中,这样在上面反序列化的时候确保可以把字段的值给恢复回来。记住不要忘记调用 base.GetObjectData(info, context), 确保基类的字段数据能正确的被序列化。

    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);
}

再次运行单元测试,这次顺利的通过了,说明 MessageErrorCode 字段在反序列化后成功的被恢复了。

总结

自定义异常是大家日常编码过程中非常常见的操作。但是看来要写好一个自定义异常类也不是那么简单。总结一下需要注意以下几点:

  1. 添加 [Serializable] Attribute
  2. 遵守微软的 guideline,特别是构造器部分 Designing Custom Exceptions Guideline
  3. 在序列化构造器对字段值进行恢复,不要忘记调用基类的序列化构造器
  4. 重写 GetObjectData 方法,把需要序列化的字段添加到 SerializationInfo 对象上,同样不要忘记调用基类的 GetObjectData

    这个问题虽然在自定义 Exception 上暴露出来,其实可以推广到所有实现 ISerializable 接口的类都需要注意 3,4 两点。

关注我的公众号一起玩转技术

如何正确实现一个自定义 Exception的更多相关文章

  1. Java自定义Exception

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...

  2. netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端

    本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...

  3. 如果你想深刻理解ASP.NET Core请求处理管道,可以试着写一个自定义的Server

    我们在上面对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了详细介绍(<聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer& ...

  4. 自定义Exception:MVC抛出自定义异常,并以Json方式返回

    相关链接 优点: 可以统一处理所有页面的异常,对所有需要返回json数据的异常,都用同样的方法throw new DVMException().页面展示,controller的错误处理方式一样 节省编 ...

  5. springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。

    springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑. 1.1 异常处理思路 系统中异常包括两类:预期异常和运行时异常RuntimeEx ...

  6. 【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)

    [SpringBoot 基础系列]实现一个自定义配置加载器(应用篇) Spring 中提供了@Value注解,用来绑定配置,可以实现从配置文件中,读取对应的配置并赋值给成员变量:某些时候,我们的配置可 ...

  7. 使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)

    译文来源 欢迎阅读如何使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器系列的第二章节, 在这之前建议您阅读使用 TypeScrip ...

  8. 一个自定义的c++错误类 和 同步异步、阻塞非阻塞(区别简述)

    一个例子,自定义exception 继承std::exception 1 class _oct_udp_api_export_ udp_err : public std::exception 2 { ...

  9. 一个自定义 HBase Filter -“通过RowKeys来高性能获取数据”

    摘要: 大家在使用HBase和Solr搭建系统中经常遇到的一个问题就是:“我通过SOLR得到了RowKeys后,该怎样去HBase上取数据”.使用现有的Filter性能差劲,网上也没有现成的自定义Fi ...

  10. 使用xib封装一个自定义view的步骤

    使用xib封装一个自定义view的步骤 1> 新建一个继承UIView的自定义view,假设类名叫做(MJAppView) 2> 新建一个MJAppView.xib文件来描述MJAppVi ...

随机推荐

  1. Velocity 不用愁!Velocity 系统的前端工程化之路

    Velocity是一个基于Java的Web页面模版引擎.十多年前,Velocity将Java代码从Web页面中分离出来,使得开发者能够并行网页开发和Java开发.随着十年前后端分离的浪潮涌动,回首再面 ...

  2. SVM主体思路和代码实现

    之前学习的KNN算法属于直接将所有的训练图片数据化,根据图片的像素值进行判断,最简单的NN算法是用与待判断图片的差距最小(距离最近)的那张图片的类别当做此图片的类别,我们不难看到,1NN算法的正确性很 ...

  3. CF1477E&大户爱的送分题题解

    CF1477E&大户爱的送分题题解 (CF1477E为我出的校内模拟赛的一道题--<大户爱的送分题>的待修版本) 大户爱的送分题 文件名OhtoAiFirst.cpp/.in/.o ...

  4. 又爱又恨的 Microsoft Edge!

    早在< 使用码云同步谷歌 Chrome 浏览器书签 · 语雀>中就吐槽过 win7 下安装 Microsoft Edge 一大堆错误代码的问题,一直都折腾不出个所以然.然而公司的 PC 一 ...

  5. JeeCms低代码开发平台了解及认知以及遇到的问题

    1.jeecms低代码开发平台自带标签,使用的标签延续freemarker标签或基于freemarker标签自定的标签(类似自jsp自定义标签) (1)什么是freemarker标签: FreeMar ...

  6. 如何构建您的第一部AWS机器学习服务

    目录 <如何构建您的第一部 AWS 机器学习服务> 背景介绍 随着深度学习的广泛应用于机器学习领域的各个方面,AWS 成为了一种重要的深度学习平台.作为 AWS 机器学习服务的第一部,如何 ...

  7. Java 判断一个字符串是否是对称字符串 例如"abc"不是对称字符串,"aba"、"abba"、"aaa"、"mnanm"是对称字符串

    代码如下: public static void main(String[] args) { String str = "1QabcdcbaQ"; StringBuilder sb ...

  8. Linux下ElasticSearch及IK分词插件安装

    ElasticSearch及IK分词插件相关安装 一. 简介 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口. ...

  9. 基于GPT搭建私有知识库聊天机器人(一)实现原理

    1.成品演示 支持微信聊天 支持网页聊天 支持微信语音对话 支持私有知识文件训练,并针对文件提问 步骤1:准备本地文件a.txt,支持pdf.txt.markdown.ppt等 步骤2:上传a.txt ...

  10. 使用部分写时复制提升Lakehouse的 ACID Upserts性能

    使用部分写时复制提升Lakehouse的 ACID Upserts性能 译自:Fast Copy-On-Write within Apache Parquet for Data Lakehouse A ...