在C#中,为何自定义Exception子类需实现自定义序列化?
核心逻辑:Exception的序列化优先级规则
Exception类本身实现了ISerializable接口,这是.NET中显式控制序列化流程的标准接口。根据CLR的序列化规则:当一个类型继承自实现了ISerializable的基类时,CLR会优先调用子类显式实现的序列化逻辑,完全跳过基于反射的自动序列化机制。这就是你疑惑的根源——不是自动序列化不能处理自定义字段,而是它根本没机会被执行。
具体原因拆解
基类契约的强制要求
继承自Exception的自定义异常,必须遵循ISerializable的契约:实现GetObjectData方法来写入自定义字段,同时实现受保护的序列化构造函数来读取这些字段。如果不实现,CLR只会调用基类Exception的序列化逻辑,子类的自定义字段(枚举、字符串等)不会被纳入序列化流程,反序列化后这些字段会被重置为类型默认值(比如枚举的0、字符串的null)。版本兼容性的保障
自动反射序列化完全依赖类型的字段结构,一旦后续修改子类的字段(新增、删除、重命名),旧版本序列化的数据会直接反序列化失败或丢失数据。而自定义序列化可以手动控制哪些字段被序列化,在版本迭代时能灵活处理兼容逻辑(比如新增字段时设置默认值),避免出现序列化断裂的问题。工具规范的最佳实践约束
Visual Studio代码分析、SonarQube这类工具的强制要求,本质是遵循.NET官方的设计规范。官方文档明确指出:继承自Exception的自定义异常必须实现ISerializable相关成员,否则会被视为不符合最佳实践,隐藏序列化隐患。
自动序列化失效的示例验证
看一段简化代码:
public class CustomException : Exception { public ErrorCode ErrorCode { get; } public string ExtraInfo { get; } public CustomException(string message, ErrorCode errorCode, string extraInfo) : base(message) { ErrorCode = errorCode; ExtraInfo = extraInfo; } // 注释掉下面的自定义序列化代码,测试序列化反序列化 // protected CustomException(SerializationInfo info, StreamingContext context) : base(info, context) // { // ErrorCode = (ErrorCode)info.GetValue(nameof(ErrorCode), typeof(ErrorCode)); // ExtraInfo = info.GetString(nameof(ExtraInfo)); // } // public override void GetObjectData(SerializationInfo info, StreamingContext context) // { // base.GetObjectData(info, context); // info.AddValue(nameof(ErrorCode), ErrorCode); // info.AddValue(nameof(ExtraInfo), ExtraInfo); // } } public enum ErrorCode { InvalidInput, PermissionDenied }
如果注释掉自定义序列化代码,序列化CustomException实例后再反序列化,你会发现ErrorCode变成枚举默认值InvalidInput,ExtraInfo变成null——原本的自定义字段值完全丢失了。
内容的提问来源于stack exchange,提问作者Koen

