游乐游手机版
首页/编程语言/文章详情

c#如何自定义异常_c#自定义异常看这一篇就够了_保姆级教程

时间:2026-05-06 07:24
C 自定义异常必须继承System Exception或其子类,否则编译报错CS0155;需实现4个构造函数以支持序列化,推荐加ErrorCode等属性但须正确处理序列化,并遵循命名、注释和轻量原则。 在C 开发中,自定义异常是处理特定业务错误的常用手段。但很多开发者存在一个误区:认为只要继承Exc

C#自定义异常必须继承System.Exception或其子类,否则编译报错CS0155;需实现4个构造函数以支持序列化,推荐加ErrorCode等属性但须正确处理序列化,并遵循命名、注释和轻量原则。

c#如何自定义异常_c#自定义异常看这一篇就够了_保姆级教程

在C#开发中,自定义异常是处理特定业务错误的常用手段。但很多开发者存在一个误区:认为只要继承Exception类即可。实际上,C#自定义异常有更严格的规范:**它必须直接继承System.Exception,或者继承其任意一个子类**。这是CLR层面的硬性要求,违反此规则将直接导致编译错误CS0155: Cannot throw an object that does not inherit from 'System.Exception'

为什么自定义异常必须继承 System.Exception

这源于C#异常处理机制的底层设计。公共语言运行时(CLR)规定,所有可被throw关键字抛出的对象,其类型必须是System.Exception的派生类。你可以通过一个简单测试验证:创建一个空类class MyError { },然后尝试执行throw new MyError();,编译器会立即阻止这一操作。

  • Exception类是CLR唯一认可的“合法异常根类型”,其内部机制确保了异常构造逻辑的封闭性。
  • 注意一个历史遗留问题:避免继承ApplicationException。该类已被微软标记为[Obsolete],官方文档明确建议不再使用。
  • 如果你的异常需要体现特定的语义层次,最佳实践是继承已有的具体异常类型,例如ArgumentException(参数错误)或InvalidOperationException(无效操作),而非创建新的根类。

实现自定义异常的标准写法(含序列化支持)

在.NET Core/.NET 5+及更高版本中,序列化支持依然至关重要。如果你的异常可能用于跨进程通信、WCF、gRPC或日志持久化场景,忽略序列化构造函数将导致异常信息在传输或存储过程中丢失。

一个健壮且符合规范的自定义异常类,通常需要实现以下四个核心构造函数:

  • 无参构造函数:主要供反序列化机制调用。
  • 接收(string message)的构造函数:最常用的场景,用于传递错误描述。
  • 接收(string message, Exception innerException)的构造函数:用于构建异常链,保留内部异常的根本原因。
  • 受保护的(SerializationInfo info, StreamingContext context)构造函数:确保在.NET Framework及需要显式序列化的.NET Core场景中正常工作。

以下是一个标准的自定义异常实现示例:

public class InvalidOrderStateException : Exception
{
    public InvalidOrderStateException() { }
    public InvalidOrderStateException(string message) : base(message) { }
    public InvalidOrderStateException(string message, Exception innerException) 
        : base(message, innerException) { }
    protected InvalidOrderStateException(SerializationInfo info, StreamingContext context) 
        : base(info, context) { }
}

如何为自定义异常添加额外属性

在许多业务场景中,仅靠错误信息不足以定位问题。为异常添加额外属性(如ErrorCodeOrderIdUserId)能极大提升调试效率。但需注意以下关键点:

  • 所有自定义属性必须在**每一个**构造函数中被正确初始化。特别是在反序列化构造函数中,必须从info参数中读取并还原属性值,否则序列化/反序列化后属性将变为默认值。
  • 如果附加信息仅用于辅助日志记录或临时调试,更推荐使用Exception.Data字典属性。它无需修改异常类结构,且会自动参与序列化过程。
  • 安全警告:切勿将敏感信息(如用户密码、API密钥、访问令牌)存入异常属性。异常对象可能被记录到明文日志或意外暴露给客户端,导致安全风险。

以下是一个安全添加ErrorCode属性的完整示例:

public class PaymentFailedException : Exception
{
    public int ErrorCode { get; }

    public PaymentFailedException(int errorCode, string message) 
        : base(message)
    {
        ErrorCode = errorCode;
    }

    protected PaymentFailedException(SerializationInfo info, StreamingContext context) 
        : base(info, context)
    {
        ErrorCode = info.GetInt32(nameof(ErrorCode));
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(nameof(ErrorCode), ErrorCode);
        base.GetObjectData(info, context);
    }
}

提升自定义异常工程化水平的细节

要编写专业级的自定义异常,还需关注以下实践细节:

  • [Serializable]特性:在.NET Framework中必须显式添加;在.NET Core/.NET 5+中虽非强制,但若缺失会导致GetObjectData方法不被调用,自定义序列化逻辑失效。
  • 添加XML注释:这不仅提升代码可读性,更能在Visual Studio智能提示、IntelliSense及生成的API文档中直接显示,对团队协作与代码维护至关重要。
  • 命名规范:异常类名应以Exception结尾,这是.NET命名约定,也是Roslyn Analyzer等代码分析工具的检查项。
  • 保持轻量:避免在异常构造函数中执行耗时操作(如文件I/O、网络请求、复杂计算)。异常对象的创建应快速、无副作用。

最后,理解异常的本质比记住所有语法更重要:**异常一旦被抛出,即标志着正常的程序流程已中断。其核心职责是清晰、准确地传达“错误类型、发生位置及根本原因”,而非执行修复或重试逻辑。** 把握这一原则,你的异常设计将更加清晰、有效。

来源:https://www.php.cn/faq/2315390.html
上一篇计算购物车中各好友应付金额的聚合方法 下一篇如何在 Laravel 中高效提取数据库查询结果的指定字段值并转为纯数组
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
列表遍历时动态判断阈值并返回相应文本
编程语言 · 2026-07-08

列表遍历时动态判断阈值并返回相应文本

遍历数值列表时,先筛选满足阈值的元素,再根据结果输出列表或友好提示。推荐使用列表推导式结合条件判断,注意边界用`>=`,空列表自动为假。也可用`any()`提前终止遍历,提升效率。此法简洁,避免显式循环,特别适合阈值筛选。

Maven项目中如何强制使用本地构建的依赖版本
编程语言 · 2026-07-08

Maven项目中如何强制使用本地构建的依赖版本

多模块开发中强制使用本地依赖的正确做法是使用-SNAPSHOT版本并配合`-U`参数强制更新,或重构为多模块项目统一管理生命周期。避免使用版本范围语法或手动复制JAR,确保构建行为可靠可重复。

Go语言net.Conn并发写安全与原子性保障解析
编程语言 · 2026-07-08

Go语言net.Conn并发写安全与原子性保障解析

Go标准库中net Conn支持并发方法调用,但Write()不保证原子性。多goroutine同时写入时,系统可能拆分数据包,导致内容交错,破坏消息边界。必须使用互斥锁或bufio Writer等显式同步机制确保写操作完整性,不可依赖系统调用本身的原子性。

Python在Windows系统中获取指定卷标U盘驱动器字母的方法
编程语言 · 2026-07-08

Python在Windows系统中获取指定卷标U盘驱动器字母的方法

使用Pythonwmi库通过Win32_Volume接口查询Windows系统中卷标为“TOSHIBA”的U盘盘符。需安装wmi和pywin32,注意大小写区分及多设备过滤。仅适用于Windows。

TP6.0消息已读功能基于Redis Bitmap未读计数方案
编程语言 · 2026-07-08

TP6.0消息已读功能基于Redis Bitmap未读计数方案

TP6 0中使用Redis位图实现消息已读标记,每条消息仅占一个比特,内存效率高。需将消息ID映射为连续偏移量,通过SETBIT和GETBIT操作。需提前维护用户ID到偏移量的映射表,注意键过期与驱动类型(phpredis与predis)问题。此方法内存极省,适合海量消息场景,且需确保偏移量唯一。