如何正确实现一个自定义 Exception

这篇具有很好参考价值的文章主要介绍了如何正确实现一个自定义 Exception。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近在公司的项目中,编写了几个自定义的 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 字段在反序列化后成功的被恢复了。

总结

自定义异常是大家日常编码过程中非常常见的操作。但是看来要写好一个自定义异常类也不是那么简单。总结一下需要注意以下几点:文章来源地址https://www.toymoban.com/news/detail-692423.html

  1. 添加 [Serializable] Attribute
  2. 遵守微软的 guideline,特别是构造器部分 Designing Custom Exceptions Guideline
  3. 在序列化构造器对字段值进行恢复,不要忘记调用基类的序列化构造器
  4. 重写 GetObjectData 方法,把需要序列化的字段添加到 SerializationInfo 对象上,同样不要忘记调用基类的 GetObjectData
    这个问题虽然在自定义 Exception 上暴露出来,其实可以推广到所有实现 ISerializable 接口的类都需要注意 3,4 两点。

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

到了这里,关于如何正确实现一个自定义 Exception的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • angular中如何定义一个全局组件?

    需求,我们需要新建一个navBreadcrumb的全局组件。这是一个面包屑导航,在不同的页面引入时传入一个路由数组即可。 第一步:我们新建这个组件: 这里有几个要注意的点: 1 ---nav-breadcrumb.module----------这个文件时一定要的。angular和vue/react不同,modulecomponent,对于任何页面而

    2024年02月12日
    浏览(38)
  • python如何定义一个简单的队列

    哈哈,其实根本不需要用 类 (class)去定义一个 队列 (queue),因为我没有想到 list 结构竟然这么万能,它本身就是一个活生生的 队列 。 1.定义一个队列,其实就是一个列表哈哈,说明一下,队头在前面,队尾在后面。 即,这个队列a对应如下图示: 2.入队 insert tail ,这个大家

    2023年04月08日
    浏览(44)
  • 如何写一个react自定义的hooks?

    使用过useState,也使用过ahook的useSetState,我们知道useState和useSetState左边赋值的第一个参数是接收初始值的变量和设置变量的方法,这里左边用的是数组解构实现的。所以,这里我们想一下,如何自定义一个hooks,也可以像useState和useSetState那样呢? 按我个人的理解,react自定义

    2024年03月15日
    浏览(39)
  • 【操作日志】如何在一个SpringBoot+Mybatis的项目中设计一个自定义ChangeLog记录?

    设计一个业务改动信息时的自定义记录,例如新增、修改、删除数据等。并且记录的规则可以通过配置的方式控制。大家需要根据各自业务场景参考,欢迎讨论。伪代码如下: 实体类:   DAO层:     自定义注解:   Http接口请求参数:   Http接口:   SQL拦截器:   Spring切面

    2024年02月06日
    浏览(41)
  • Java中的异常Exception和捕获,自定义异常

    1.1 什么是程序的异常  在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的。 异常 :指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停

    2023年04月20日
    浏览(41)
  • python 教你如何创建一个自定义库 colorlib.py

    目录 Colorlib 生成代码 模块代码 导入测试 测试一 测试二 应用测试 颜色列表 colorList 随机颜色元组 randcolorTuples 随机颜色字串 randcolorStrings Color类测试 测试一 测试二 题外话 有没有碰到过这样的场景:写代码时想要用上丰富的色彩,但苦思冥想搜肠刮肚只记得这几个常用颜色

    2024年03月23日
    浏览(42)
  • 如何优雅地创建一个自定义的Spring Boot Starter

    优雅永不过时,希望看完本文,你会觉得starter如此优雅! Spring Boot Starter是一种简化Spring Boot应用开发的机制,它可以通过引入一些预定义的依赖和配置,让我们快速地集成某些功能模块,而无需繁琐地编写代码和配置文件。Spring Boot官方提供了很多常用的Starter,例如 spring

    2024年02月11日
    浏览(57)
  • Java lambda表达式如何自定义一个toList Collector

    匿名类: Combiner: 应用: 优化初始容器的容量: Jdk toList默认实现:

    2024年02月01日
    浏览(41)
  • 如何实现设备组缓存的正确清除?——基于心跳请求和心跳响应的解决方案

    在设备组关闭后,如何保证缓存中的设备组信息能够正确清除?本文将介绍如何通过前端实现设备组心跳检测和缓存清除,以及通过后端实现缓存清除的逻辑来解决该问题。我们还将详细讨论如何利用心跳请求和心跳响应来实现设备组缓存的正确清除,并提供基于Vue和Spring

    2024年02月07日
    浏览(46)
  • Baumer工业相机堡盟工业相机如何通过NEOAPISDK实现相机资源的正确释放(C#)

    Baumer工业相机堡盟相机是一种高性能、高质量的工业相机,可用于各种应用场景,如物体检测、计数和识别、运动分析和图像处理。 Baumer的万兆网相机拥有出色的图像处理性能,可以实时传输高分辨率图像。此外,该相机还具有快速数据传输、低功耗、易于集成以及高度可扩

    2024年04月27日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包