最近在公司的项目中,编写了几个自定义的 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. vue中嵌入MP4 只有声音没图像

    最近一个项目需要在页面嵌入一段视频,当然首选iframe了,直接嵌入了youku的视频,没问题,我想ok了.于是将url替换为本地的MP4发现只有声音没有任何图片,奇怪了,我首先想到是不是vue项目使 ...

  2. Excel DDE Commands

    ! https://zhuanlan.zhihu.com/p/635569763 Excel DDE Commands 连接参数 Application: Excel Topic: System: 整 ...

  3. CSS 图片加载提前占位 padding-top、padding-bottom

    今天聊一个图片加载提前占位的一个问题 ,内容比较适合初学者. 起因 在响应式页面当中,图片加载之前是不知道图片高度的,加载成功图片完全撑开.如果不做提前占位会把下面的内容挤下去,页面出现抖动,就像下面 ...

  4. python测试系列教程——python+Selenium+chrome自动化测试框架

    需要的环境 浏览器(Firefox/Chrome/IE-) Python Selenium Selenium IDE(如果用Firefox) FireBug.FirePath(如果用Firefox) ...

  5. Spark架构与运行流程

    1. 阐述Hadoop生态系统中,HDFS, MapReduce, Yarn, Hbase及Spark的相互关系. 2. Spark已打造出结构一体化.功能多样化的大数据生态系统,请简述Spark生态 ...

  6. C++调用tensorflow模型

    C++ 和python的混合编程 windows + vs 新建一个工程,在工程属性中添加如下的几个 C:\Users\[user_name]\Anaconda3\include C:\Users\[ ...

  7. GPT3的性能评估:比较不同语言、文本和任务的差异

    目录 GPT-3 性能评估:比较不同语言.文本和任务的差异 近年来,自然语言处理 (NLP) 和人工智能领域取得了巨大的进展,其中 GPT-3 是目前最为先进的语言模型之一.GPT-3 拥有超过 17 ...

  8. C++面试八股文:什么是空指针/野指针/悬垂指针?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第30面: 面试官:什么是空指针? 二师兄:一般我们将等于0/NULL/nullptr的指针称为空指针.空指针不能被解引用,但是可以对空指针取地址. ...

  9. JVM之指针压缩

    做java开发的同学一般都比较熟悉JVM,那么关于指针压缩这块内容是不是也了解呢,不熟悉的小伙伴往下看吧. 首先说明,本文涉及的JDK版本是1.8,JVM虚拟机是64位的HotSpot实现为准. ja ...

  10. SQL SERVER 拼接字符串转化为表结构数据

    本文为一些需要对特殊符号分隔的字符串进行解析,比如将 select '10,20,30,40,50,60' 这个字符串转化为一列多行 下面提供源代码: 1 SET QUOTED_IDENTIFIER ...