如何在protobuf-net中为自定义Option<T>实现类似Nullable<T>的序列化规则
实现方案
protobuf-net完全支持自定义序列化规则,可实现你需要的和Nullable<T>一致的序列化效果,以下是两种可用方案:
方案1:值转换器+代理注册(无默认值冲突,推荐)
该方案不会和T类型的默认有效值产生冲突,行为和Nullable<T>100%一致。
步骤1:修正Option代码(你提供的示例存在语法错误)
public class Option<T> { private bool _hasValue; private T _value; private Option(T val) { _value = val; _hasValue = true; } private Option(bool hasValue) { _hasValue = hasValue; _value = default; } public static Option<T> Value(T val) => new Option<T>(val); public static Option<T> Nothing() => new Option<T>(false); public bool HasValue => _hasValue; public T Value => _hasValue ? _value : throw new InvalidOperationException("当前Option无值"); }
步骤2:注册序列化规则
在程序启动时为所有用到的Option<T>封闭泛型类型注册规则,示例如下:
using ProtoBuf.Meta; public static class ProtobufOptionConfig { public static void RegisterOption<T>(RuntimeTypeModel model = null) { model ??= RuntimeTypeModel.Default; // 注册值转换器,实现Option<T>和T的双向转换 model.ValueConverters.Add(new ValueConverter<Option<T>, T>( serialize: opt => opt.HasValue ? opt.Value : default, deserialize: val => // 直接判断是否存在输入值,不会和T的默认有效值冲突 RuntimeTypeModel.Default.IsDeserializingEmptyValue ? Option<T>.Nothing() : Option<T>.Value(val) )); // 配置Option<T>的默认值为Nothing,为空时不输出任何内容 model.Add<Option<T>>(false).SetDefaultValue(Option<T>.Nothing()); } } // 启动时调用,比如你用到了Option<int>、Option<string>、Option<DateTime> ProtobufOptionConfig.RegisterOption<int>(); ProtobufOptionConfig.RegisterOption<string>(); ProtobufOptionConfig.RegisterOption<DateTime>();
方案2:特性标记方案(无需启动配置,简单)
如果你的业务场景中不会将T类型的默认值作为有效取值,可直接通过添加protobuf-net原生特性实现,无需额外启动配置:
using ProtoBuf; [ProtoContract(SkipConstructor = true, IgnoreListHandling = true)] public class Option<T> { [ProtoMember(1)] private T _value; private bool _hasValue; private Option(T val) { _value = val; _hasValue = true; } private Option(bool hasValue) { _hasValue = hasValue; _value = default; } public static Option<T> Value(T val) => new Option<T>(val); public static Option<T> Nothing() => new Option<T>(false); // protobuf-net自动识别的条件序列化方法,返回false时字段不输出 public bool ShouldSerialize_value() => _hasValue; // 反序列化完成后的回调,自动设置_hasValue状态 [ProtoAfterDeserialization] private void AfterDeserialization() { _hasValue = !EqualityComparer<T>.Default.Equals(_value, default); } }
注意:该方案存在边界问题,比如
Option<int>.Value(0)会被识别为默认值,反序列化后会变成Nothing,仅适用于默认值无效的业务场景。
内容的提问来源于stack exchange,提问作者d--b
相关产品推荐
相关产品推荐

