Protobuf-net枚举向后兼容性

时间:2016-02-24 11:12:47

标签: c# enums protocol-buffers protobuf-net backwards-compatibility

我试图在新的应用程序版本中为某个protobuf序列化类添加新的枚举值,并且在测试时,注意到以前的版本将抛出异常,给定这种新的文件格式:

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

很明显,它告诉我int的{​​{1}}值没有枚举值,但我始终认为Protocol Buffers defaulted to the zero-valued ("default") enum value(如果如果实际的枚举值无法映射到。

为了澄清,可以使用以下示例重现这一点(我有意将反序列化步骤转换为另一个类,以模仿试图加载新格式的旧应用程序):

3

以下代码将失败:

// --- 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; }
}

由于v1已经打开,在v2中序列化时是否可以使用一些元数据以避免此问题?当然,我可以通过重写v2以使用单独的属性并使枚举值保持不变来让自己摆脱这个麻烦。但是如果可能的话,我想让枚举向后兼容。

4 个答案:

答案 0 :(得分:3)

在您的枚举中添加[ProtoContract(EnumPassthru=true)]将允许protobuf-net反序列化未知值。

很遗憾,无法追溯修复您的v1。你必须使用不同的财产。

答案 1 :(得分:1)

  

由于v1已经打开,在v2中序列化时是否可以使用一些元数据以避免此问题?当然,我可以通过重写v2以使用单独的属性并使枚举值保持不变来让自己摆脱这个麻烦。但是如果可能的话,我想让枚举向后兼容。

您遇到的是此处描述的 protobuf-net 错误protobuf-net - issue #422: Invalid behaviour while deserializing unknown enum value

根据protobuf-net faulty enum exception (issue 422) need a good workaround(当然还有你的帖子),它似乎尚未修复。

不幸的是,您需要修复protobuf-net源代码或使用提到的解决方法。

更新:我已检查GitHub存储库中的代码并确认问题仍未修复。以下是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
}

答案 2 :(得分:0)

你的ClassV1缺乏向前兼容性。

我会以这样的方式实现Proto契约,它序列化/反序列化枚举值的字符串表示。这样您就可以自己处理回退到默认值。 Value属性不会被序列化/反序列化。

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; }
}

仍然没有解决生产中具有非正向可比类的问题。

答案 3 :(得分:0)

您可以将DefaultValue属性添加到proto成员属性。

[ProtoContract]
public class ClassV1
{
    [ProtoMember(1), DefaultValue(EnumV1.Default)]
    public EnumV1 Value { get; set; }
}

要明确如何为默认情况初始化属性。