.NET中如何将自定义对象序列化为原始类型
我现在有一堆原本是简单整数的ID,为了获得编译器的类型检查支持,我把它们封装成了强类型的结构体,代码如下:
public struct CustomId { private readonly int id; public CustomId(int id) => this.id = id; public static implicit operator int(CustomId cid) => cid.id; public static implicit operator CustomId(int id) => new CustomId(id); }
这个结构体本质就是带特定名称的int,我希望在序列化的时候能直接输出成int类型(比如JSON里Person对象的Id字段直接显示为42而不是嵌套的对象)。目前我已经给Newtonsoft JSON写了自定义的JsonConverter并且能用,但想找一个更通用的方案——比如实现System.Runtime.Serialization命名空间下的特性或接口,让多数序列化框架(JSON、XML、Protobuf等)都能兼容,不用为每种框架单独写转换器。有没有这样的方案?
好问题!确实有几个通用的方案能让你的强类型ID在多数序列化框架下自动被当作基础int处理,不用逐个适配:
1. 实现 ISerializable 接口(通用序列化契约)
System.Runtime.Serialization.ISerializable 是.NET原生的通用序列化接口,很多框架(包括BinaryFormatter、XML Serializer,甚至部分主流JSON库)都会识别它。你只需要在结构体里实现这个接口,指定序列化时只输出内部的int值:
using System.Runtime.Serialization; public struct CustomId : ISerializable { private readonly int id; public CustomId(int id) => this.id = id; // 保留原有的隐式转换逻辑 public static implicit operator int(CustomId cid) => cid.id; public static implicit operator CustomId(int id) => new CustomId(id); // ISerializable 核心实现:定义序列化时输出的内容 public void GetObjectData(SerializationInfo info, StreamingContext context) { // 直接把内部int值作为当前对象的序列化内容 info.AddValue("value", id); } // 必须的反序列化构造函数,否则框架无法还原对象 private CustomId(SerializationInfo info, StreamingContext context) { id = info.GetInt32("value"); } }
这个方案的优势是通用性极强,只要序列化框架兼容ISerializable,就能自动完成CustomId和int的序列化/反序列化转换。
2. 使用 DataContract 和 DataMember 特性
如果你的序列化框架支持WCF数据契约规范(比如DataContractSerializer、Newtonsoft JSON、Protobuf-net等),可以用[DataContract]和[DataMember]特性简化配置,让框架直接序列化内部的int字段:
using System.Runtime.Serialization; [DataContract] public struct CustomId { // 标记内部int字段为序列化成员,可自定义名称适配业务场景 [DataMember(Name = "Id")] private readonly int id; public CustomId(int id) => this.id = id; // 保留隐式转换逻辑 public static implicit operator int(CustomId cid) => cid.id; public static implicit operator CustomId(int id) => new CustomId(id); }
这个写法更简洁,而且多数现代序列化框架对DataContract特性有原生支持,包括XML序列化、JSON序列化,甚至Protobuf-net也能通过配置快速识别。
3. 添加 TypeConverter 适配.NET类型系统
你还可以给CustomId添加一个TypeConverter,让.NET类型系统自动识别它和int的转换关系,这样不仅序列化框架能受益,其他.NET组件(比如UI控件、配置系统)也能自动处理类型转换:
using System.ComponentModel; [TypeConverter(typeof(CustomIdConverter))] public struct CustomId { private readonly int id; public CustomId(int id) => this.id = id; // 保留隐式转换逻辑 public static implicit operator int(CustomId cid) => cid.id; public static implicit operator CustomId(int id) => new CustomId(id); // 自定义类型转换器 private class CustomIdConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(int) || base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (value is int intValue) return new CustomId(intValue); return base.ConvertFrom(context, culture, value); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return destinationType == typeof(int) || base.CanConvertTo(context, destinationType); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(int) && value is CustomId customId) return customId.id; return base.ConvertTo(context, culture, value, destinationType); } } }
额外小技巧(.NET 6+)
如果你用的是.NET 6及以上版本,可以用record struct简化强类型ID的写法,同时结合上面的方案,代码会更简洁:
[DataContract] public record struct CustomId([property: DataMember] int Value) { public static implicit operator int(CustomId cid) => cid.Value; public static implicit operator CustomId(int id) => new(id); }
内容的提问来源于stack exchange,提问作者Emil

