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

很明显,它告诉我 3int 值没有枚举值,但我一直认为 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
}