Protobuf-net 枚举向后兼容性
Protobuf-net enum backwards compatibility
我试图在一个新的应用程序版本中为某个 protobuf 序列化 class 添加一个新的枚举值,并且在测试时,注意到以前的版本会抛出一个异常,给定这个新的文件格式:
An unhandled exception of type 'ProtoBuf.ProtoException' occurred in protobuf-net.dll
Additional information: No {enum-type-name} enum is mapped to the wire-value 3
很明显,它告诉我 3
的 int
值没有枚举值,但我一直认为 Protocol Buffers defaulted to the zero-valued ("default") enum value(如果存在的话) ),以防无法映射到实际的枚举值。
为了澄清,这可以使用以下示例重现(我有意将反序列化步骤执行到不同的 class 以模仿旧应用程序尝试加载新格式):
// --- version 1 ---
public enum EnumV1
{
Default = 0,
One = 1,
Two = 2
}
[ProtoContract]
public class ClassV1
{
[ProtoMember(1)]
public EnumV1 Value { get; set; }
}
// --- version 2 ---
public enum EnumV2
{
Default = 0,
One = 1,
Two = 2,
Three = 3 // <- newly added
}
[ProtoContract]
public class ClassV2
{
[ProtoMember(1)]
public EnumV2 Value { get; set; }
}
下面的代码会失败:
// serialize v2 using the new app
var v2 = new ClassV2() { Value = EnumV2.Three };
var v2data = Serialize(v2);
// try to deserialize this inside the old app to v1
var v1roundtrip = Deserialize<ClassV1>(v2data);
由于 v1 是公开的,在 v2 中序列化时是否可以使用一些元数据来避免这个问题?当然,我可以通过重写 v2 以使用单独的 属性 并保持枚举值不变来摆脱这个麻烦,但如果可能的话,我想让枚举向后兼容。
您的 ClassV1 缺乏向前兼容性。
我会以 serializes/deserializes 枚举值的字符串表示形式实现 Proto 合约。这样你就可以自己处理回退到默认值。值 属性 不会是 serialized/deserialized。
public enum EnumV1
{
Default = 0,
One = 1,
Two = 2
}
public enum EnumV2
{
Default = 0,
One = 1,
Two = 2,
Three = 3 // <- newly added
}
[ProtoContract]
public class ClassV1
{
[ProtoMember(1)]
public string ValueAsString
{
get { return Value.ToString(); }
set
{
try
{
Value = (EnumV1) Enum.Parse(typeof (EnumV1), value);
}
catch (Exception)
{
Value = EnumV1.Default;
}
}
}
public EnumV1 Value { get; set; }
}
[ProtoContract]
public class ClassV2
{
[ProtoMember(1)]
public string ValueAsString
{
get { return Value.ToString(); }
set
{
try
{
Value = (EnumV2)Enum.Parse(typeof(EnumV2), value);
}
catch (Exception)
{
Value = EnumV2.Default;
}
}
}
public EnumV2 Value { get; set; }
}
它仍然没有解决在生产中具有非向前兼容 class 的问题。
您可以将 DefaultValue 属性添加到您的原型成员 属性。
[ProtoContract]
public class ClassV1
{
[ProtoMember(1), DefaultValue(EnumV1.Default)]
public EnumV1 Value { get; set; }
}
明确 属性 应如何针对默认情况进行初始化。
将 [ProtoContract(EnumPassthru=true)]
添加到您的枚举将允许 protobuf-net 反序列化未知值。
遗憾的是,无法追溯修复您的 v1.您必须使用不同的 属性.
Since v1 is out in the open, is there some metadata I can use when serializing in v2 to avoid this issue? I can, of course, get myself out of this trouble by rewriting v2 to use a separate property and leave the enum values unmodified, but I'd like to make enums backwards compatible if possible.
您遇到的是 protobuf-net 此处描述的错误 protobuf-net - issue #422: Invalid behaviour while deserializing unknown enum value。
根据此处protobuf-net faulty enum exception (issue 422) need a good workaround(当然还有您的post),似乎尚未修复。
很遗憾,您需要修复 protobuf-net
源代码或使用提到的解决方法。
更新: 我检查了 GitHub repository and confirming that the issue is still not fixed. Here is the problematic code inside the EnumSerializer.cs 中的代码(ISSUE #422
评论是我的):
public object Read(object value, ProtoReader source)
{
Helpers.DebugAssert(value == null); // since replaces
int wireValue = source.ReadInt32();
if(map == null) {
return WireToEnum(wireValue);
}
for(int i = 0 ; i < map.Length ; i++) {
if(map[i].WireValue == wireValue) {
return map[i].TypedValue;
}
}
// ISSUE #422
source.ThrowEnumException(ExpectedType, wireValue);
return null; // to make compiler happy
}
我试图在一个新的应用程序版本中为某个 protobuf 序列化 class 添加一个新的枚举值,并且在测试时,注意到以前的版本会抛出一个异常,给定这个新的文件格式:
An unhandled exception of type 'ProtoBuf.ProtoException' occurred in protobuf-net.dll Additional information: No {enum-type-name} enum is mapped to the wire-value 3
很明显,它告诉我 3
的 int
值没有枚举值,但我一直认为 Protocol Buffers defaulted to the zero-valued ("default") enum value(如果存在的话) ),以防无法映射到实际的枚举值。
为了澄清,这可以使用以下示例重现(我有意将反序列化步骤执行到不同的 class 以模仿旧应用程序尝试加载新格式):
// --- version 1 ---
public enum EnumV1
{
Default = 0,
One = 1,
Two = 2
}
[ProtoContract]
public class ClassV1
{
[ProtoMember(1)]
public EnumV1 Value { get; set; }
}
// --- version 2 ---
public enum EnumV2
{
Default = 0,
One = 1,
Two = 2,
Three = 3 // <- newly added
}
[ProtoContract]
public class ClassV2
{
[ProtoMember(1)]
public EnumV2 Value { get; set; }
}
下面的代码会失败:
// serialize v2 using the new app
var v2 = new ClassV2() { Value = EnumV2.Three };
var v2data = Serialize(v2);
// try to deserialize this inside the old app to v1
var v1roundtrip = Deserialize<ClassV1>(v2data);
由于 v1 是公开的,在 v2 中序列化时是否可以使用一些元数据来避免这个问题?当然,我可以通过重写 v2 以使用单独的 属性 并保持枚举值不变来摆脱这个麻烦,但如果可能的话,我想让枚举向后兼容。
您的 ClassV1 缺乏向前兼容性。
我会以 serializes/deserializes 枚举值的字符串表示形式实现 Proto 合约。这样你就可以自己处理回退到默认值。值 属性 不会是 serialized/deserialized。
public enum EnumV1
{
Default = 0,
One = 1,
Two = 2
}
public enum EnumV2
{
Default = 0,
One = 1,
Two = 2,
Three = 3 // <- newly added
}
[ProtoContract]
public class ClassV1
{
[ProtoMember(1)]
public string ValueAsString
{
get { return Value.ToString(); }
set
{
try
{
Value = (EnumV1) Enum.Parse(typeof (EnumV1), value);
}
catch (Exception)
{
Value = EnumV1.Default;
}
}
}
public EnumV1 Value { get; set; }
}
[ProtoContract]
public class ClassV2
{
[ProtoMember(1)]
public string ValueAsString
{
get { return Value.ToString(); }
set
{
try
{
Value = (EnumV2)Enum.Parse(typeof(EnumV2), value);
}
catch (Exception)
{
Value = EnumV2.Default;
}
}
}
public EnumV2 Value { get; set; }
}
它仍然没有解决在生产中具有非向前兼容 class 的问题。
您可以将 DefaultValue 属性添加到您的原型成员 属性。
[ProtoContract]
public class ClassV1
{
[ProtoMember(1), DefaultValue(EnumV1.Default)]
public EnumV1 Value { get; set; }
}
明确 属性 应如何针对默认情况进行初始化。
将 [ProtoContract(EnumPassthru=true)]
添加到您的枚举将允许 protobuf-net 反序列化未知值。
遗憾的是,无法追溯修复您的 v1.您必须使用不同的 属性.
Since v1 is out in the open, is there some metadata I can use when serializing in v2 to avoid this issue? I can, of course, get myself out of this trouble by rewriting v2 to use a separate property and leave the enum values unmodified, but I'd like to make enums backwards compatible if possible.
您遇到的是 protobuf-net 此处描述的错误 protobuf-net - issue #422: Invalid behaviour while deserializing unknown enum value。
根据此处protobuf-net faulty enum exception (issue 422) need a good workaround(当然还有您的post),似乎尚未修复。
很遗憾,您需要修复 protobuf-net
源代码或使用提到的解决方法。
更新: 我检查了 GitHub repository and confirming that the issue is still not fixed. Here is the problematic code inside the EnumSerializer.cs 中的代码(ISSUE #422
评论是我的):
public object Read(object value, ProtoReader source)
{
Helpers.DebugAssert(value == null); // since replaces
int wireValue = source.ReadInt32();
if(map == null) {
return WireToEnum(wireValue);
}
for(int i = 0 ; i < map.Length ; i++) {
if(map[i].WireValue == wireValue) {
return map[i].TypedValue;
}
}
// ISSUE #422
source.ThrowEnumException(ExpectedType, wireValue);
return null; // to make compiler happy
}