c#自定义异常必须继承system.exception或其子类,否则编译报错cs0155;需实现4个构造函数以支持序列化,且自定义字段如errorcode须在getobjectdata和反序列化构造函数中闭环处理。

自定义异常不是“写个类 throw 就完事”,不满足 CLR 的类型约束或漏掉序列化支持,编译就报 CS0155,跨进程/远程调用时直接丢字段、断堆栈。
为什么 throw new MyException() 会编译失败?
CLR 层硬性要求所有可抛出对象必须是 System.Exception 或其任意子类的实例。哪怕你只写 class MyException { },throw new MyException() 就会触发编译错误:CS0155: Cannot throw an object that does not inherit from 'System.Exception'。
这不是语法建议,是运行时机制决定的强制契约。
-
ApplicationException已被微软标记为[Obsolete],文档明确说“不要使用” - 若语义上属于参数问题,优先继承
ArgumentException;属于状态非法,继承InvalidOperationException;别凭空造根类 - 继承
Exception是最稳妥起点,但必须补全序列化构造函数,否则 .NET Core/.NET 5+ 下日志捕获、gRPC 透传时属性为空
必须实现哪四个构造函数?
缺任何一个,都可能在反序列化(如 WCF、gRPC、跨 AppDomain)时失败,或导致 ErrorCode 等业务字段为默认值(0 或 null)。
- 无参构造函数:供反序列化框架调用(如 JSON.NET、System.Text.Json 在特定配置下)
-
MyException(string message):日常最常用 -
MyException(string message, Exception innerException):链式异常场景,保留原始上下文 -
protected MyException(SerializationInfo info, StreamingContext context):反序列化入口,必须调用base(info, context),且手动读取自定义字段
示例中 ErrorCode 字段在反序列化构造函数里必须显式读取:
protected MyException(SerializationInfo info, StreamingContext context) : base(info, context)
{
ErrorCode = info.GetInt32(nameof(ErrorCode));
}
加 ErrorCode 等业务字段要注意什么?
加字段本身没问题,但序列化/反序列化路径必须闭环,否则等于没加。
- 所有构造函数(包括反序列化构造函数)都要初始化该字段,不能只在带参构造里赋值
- 必须重写
GetObjectData(),把字段写入info,否则反序列化时读不到 - 如果只是临时调试用,优先用
Exception.Data字典——它自动参与序列化,无需改类结构 - 避免在构造函数里做耗时操作或访问外部状态,异常实例应保持轻量、纯数据
ASP.NET Core 中怎么让 ErrorCode 出现在 HTTP 响应里?
直接 throw 自定义异常,前端收不到 ErrorCode —— 默认中间件只序列化 Message 和 StackTrace。
- 不要手动拼 JSON 或返回
JsonResult,绕过格式化器链和 OpenAPI 兼容性 - 在
UseExceptionHandler中用ProblemDetails包装,把ErrorCode放进Extensions字典 - 例如:
problemDetails.Extensions["errorCode"] = ex.ErrorCode; - 普通未捕获异常默认 status=500,但别硬塞
title="Internal Server Error",保留原ex.Message更利于定位
真正容易被忽略的是:反序列化构造函数里忘了读字段,或者 GetObjectData 里忘了写字段——这两处一漏,ErrorCode 在远程调用链里就彻底消失了。











