C# Protobuf网络枚举向后兼容性

C# Protobuf网络枚举向后兼容性,c#,enums,protocol-buffers,protobuf-net,backwards-compatibility,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 以下代码将失败: // s

我试图在新的应用程序版本中为某个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 以下代码将失败:

// 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);
//使用新应用程序序列化v2
var v2=new ClassV2(){Value=EnumV2.Three};
var v2data=Serialize(v2);
//尝试在旧应用程序中将其反序列化为v1
var v1往返=反序列化(v2data);

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

您的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; }
}
public enum EnumV1
{
默认值为0,
1=1,
二=2
}
公共枚举枚举v2
{
默认值为0,
1=1,
二等于二,

Three=3/您可以将DefaultValue属性添加到proto成员属性中

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

明确默认情况下应如何初始化属性。

向枚举添加
[ProtoContract(EnumPassthru=true)]
将允许protobuf net反序列化未知值

不幸的是,无法追溯修复v1。您必须使用不同的属性

[ProtoContract]
public class ClassV1
{
    [ProtoMember(1), DefaultValue(EnumV1.Default)]
    public EnumV1 Value { get; set; }
}
由于v1是公开的,在v2中序列化时是否可以使用一些元数据来避免此问题?当然,我可以通过重写v2以使用单独的属性并保持枚举值不变来摆脱此问题,但如果可能,我希望使枚举向后兼容

您正在经历的是这里描述的一个protobuf-net错误

根据这里(当然还有你的帖子),它似乎还没有被修复

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

更新:我已经检查了存储库中的代码,确认问题仍未解决。以下是问题代码(问题#422
注释是我的):

公共对象读取(对象值,ProtoReader源)
{
Helpers.DebugAssert(value==null);//因为
int wireValue=source.ReadInt32();
if(map==null){
返回WireToEnum(wireValue);
}
for(int i=0;i
发送
EnumV2.Three
时,
v1roundtrip.Value
会发生什么情况?@Caramiriel:根据我的理解(在中解释),它应该设置为
EnumV1.Default
,而不是引发异常。如果我想确保格式向后兼容,这就是我所期望的。例如,似乎有相同的问题,并通过添加默认值(零)来修复它enum值,不需要额外的protobuf属性。@jgauffin:我要评论的正是这一点,这似乎是Marc获取这些点的一种简单方法(我假设我没有正确配置)。有很多方法可以确保向后/向前兼容性。一种更简单的方法(对于enum)将
int
值反序列化是可行的。但这不是我所做的,因为我依赖protobuf来处理这个问题。我设计v1契约时相信协议缓冲区默认为零值枚举值,以防实际枚举值无法映射到,我想看看1)为什么这样做不可行“不起作用,2)至少在反序列化时如何拦截和处理此情况。您的回答既不能解释为什么
ClassV1
缺少fw兼容性,也不能解释现在该怎么做。缺少前向兼容性是因为ClassV1不支持新的枚举值,可以将其视为包含编译时常量的值类型。类似于在没有默认分支的情况下实现switch/case语句,并让它处理不支持的情况。我不认为设置默认值是protobuf的责任。我会让ClassV1类在实例化期间初始化属性,而不是让它单元化,从而自行处理这一问题(布尔字段也隐式初始化为false).proto buf将抛出nonthless,这很好,因为它表明存在兼容性问题。首先,protobuf应该能够处理新的枚举值。接下来,我不是在讨论默认值。显然,当我实例化属性时,该属性将初始化为默认值,而与属性无关,并且与serializat无关ion.正如protobuf的开发者指南所述,如果您有一个使用协议缓冲区作为其数据格式的通信协议,您可以扩展您的协议,而不必担心破坏现有代码。这是协议缓冲区的主要卖点之一。您尝试过吗?我认为这与异常无关。Bu这不会将反序列化值设置为
3
(即使它没有匹配的枚举),而不是默认值吗?我仍然希望在无法解析实际枚举的情况下使用默认值?是的。如果您需要旧版本上的值作为默认值,而新版本上的值作为某个新枚举值(在本例中为3),则每次向枚举添加新值时都必须切换到新属性。
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
}