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

直接说结论:C# 自定义异常不是“必须继承 Exception”,但**必须继承 System.Exception 或其任意子类**;否则 throw 时会编译报错 CS0155: Cannot throw an object that does not inherit from 'System.Exception'。
为什么一定要继承 Exception 而不是 ExceptionBase 或其他基类
C# 的异常机制在 CLR 层硬性要求所有可抛出对象必须是 System.Exception 的派生类型。哪怕你写一个空类 class MyErr { },编译器立刻拒绝:throw new MyErr(); → 报错。
-
Exception是唯一被 CLR 识别为“合法异常根类型”的类(它本身 sealed 了Exception的构造逻辑) - 不推荐继承
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) { }
}要不要加额外属性?什么时候加?
加额外属性(如 ErrorCode、OrderId)有意义,但要注意两点:
- 这些属性必须在所有构造函数中初始化(尤其反序列化构造函数里要从
info中读取),否则序列化后属性值为默认值(如 0 或 null) - 如果只用于日志或调试,更推荐用
Exception.Data字典(无需改类结构,且自动参与序列化) - 避免把敏感字段(如用户密码、token)塞进异常属性——异常可能被记录到明文日志或暴露给前端
例如补充 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);
}
}别忘了给异常加 XML 注释和 [Serializable]
[Serializable] 在 .NET Framework 必须显式添加;在 .NET Core/.NET 5+ 虽非强制,但缺少它会导致 GetObjectData 不被调用,自定义序列化逻辑失效。
- XML 注释不只是“好看”——它会在 Visual Studio 智能提示、IntelliSense 和生成的 API 文档中直接显示,对协作者极重要
- 异常类名应以
Exception结尾(这是 .NET 命名约定,也是静态分析工具如 Roslyn Analyzer 的检查项) - 避免在异常构造函数里做耗时操作(如 IO、网络请求、复杂计算)——异常本应轻量、即时创建
真正容易被忽略的是:**异常一旦抛出,就代表程序流程已中断;它的职责只是清晰传达“什么错了、在哪错、为什么错”,而不是修复问题或触发重试逻辑。** 这一点,比怎么写构造函数更重要。


















