首页 > 其他分享 >如何正确实现一个自定义 Exception

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

时间:2023-09-03 22:26:58浏览次数:45  
标签:info Exception 正确 自定义 MyException ErrorCode 序列化 public

最近在公司的项目中,编写了几个自定义的 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);
        }

再次运行单元测试,这次顺利的通过了

标签:info,Exception,正确,自定义,MyException,ErrorCode,序列化,public
From: https://www.cnblogs.com/kklldog/p/how-to-design-exception.html

相关文章

  • .NetCore——全局异常过滤器ExceptionFilterAttribute
    .NetCore——全局异常过滤器ExceptionFilterAttribute一、介绍在我们的项目运行中,当程序出现异常的时候就会弹窗大黄页,所以为了更方便的解决这个问题,我们采用全局过滤器ExceptionFilterAttribute。通过它主动捕获程序中的异常,然后经过处理再抛出信息。下面咱们直接上干货,撸起来......
  • springboot自动配置的原理和如何自定义starter
    一、springboot自动配置的原理使用springboot时的一大优点就是当需要引入一些第三方的框架时只需要引入一个对应的starter后springboot就会自动的完成配置,例如在springboot中使用mybatis只需要引入mybatis提供的starter.那么这种便捷的配置方式是如何实现的呢,要了解其中的原理......
  • uniapp项目实践总结(八)自定义加载组件
    有时候一个页面请求接口需要加载很长时间,这时候就需要一个加载页面来告知用户内容正在请求加载中,下面就写一个简单的自定义加载组件。目录准备工作逻辑思路实战演练效果预览准备工作在之前的全局组件目录components下新建一个组件文件夹,命名为q-loading,组件为q-loading......
  • asp.net restful ef core sqlite 自定义包的位置
    MagicVilla_VillaAPI/MagicVilla_VillaAPI.csproj<ProjectSdk="Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>net7.0</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>e......
  • vue自定义事件用法及$emit
    子组件<template><button@click="handle">自定义事件</button></template><script>exportdefault{data(){return{message:"我子组件"}},methods:{handle(){......
  • 自定义CUDA实现PyTorch算子的四种简单方法
    背景在探索新的深度学习算法的时候,我们可能会遇到PyTorch提供的算子不能满足需求的情况,这时候就需要自定义PyTorch算子,将我们的算法集成到PyTorch的工作流中。同时,为了提高运算效率,算子往往都需要使用CUDA实现。所幸,PyTorch及很多其他Python库都提供了简化这一过程的方法,完全不需......
  • Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: The
    这个错误表明在尝试获取JDBC连接时发生了SQLException,并且该异常表示服务器不识别或不包含一个以上的时区。以下是一些可能的解决方法:确认服务器时区设置:确保你的服务器时区设置正确。你可以在数据库中运行以下查询来检查当前时区设置:sqlSELECT@@全球化设置(‘TzSystem’);如......
  • dotnet SemanticKernel 入门 自定义变量和技能
    本文将告诉大家如何在SemanticKernel框架内定义自定义的变量和如何开发自定义的技能本文属于SemanticKernel入门系列博客,更多博客内容请参阅我的博客导航自定义变量是一个非常有用的技能,自定义变量可以让炼丹师和程序员进行并行工作。由炼丹师对AI模型进行训练,从而找到对......
  • uniapp项目实践总结(六)自定义顶部导航栏
    本篇主要讲述如何自定义顶部导航栏,有时候默认导航栏不足以满足我们的需求,这时候就需要自定义导航栏来解决这个问题。目录默认导航修改配置自定义顶部默认导航自带的默认顶部导航设置的内容有限,不容易扩展修改,因此如果有更加个性化的需求,则需要自定义顶部导航。配置如下......
  • uniapp的u-album组件自定义删除功能
    加是否显示删除按钮图片字段删除按钮删除方法删除按钮样式代码<template><viewclass="u-album"><viewclass="u-album__row"ref="u-album__row"v-for="(arr,index)inshowUrls"......