Обратная совместимость Protobuf-net enum

Я пытался добавить новое значение перечисления для определенного класса 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 стоимостью 3, но у меня всегда была идея, что буферы протокола по умолчанию имеют нулевое значение ("default") enum value (если такой существует), в случае, если фактическое значение перечисления не может быть сопоставлено.

чтобы уточнить, это можно воспроизвести, используя следующий пример (я намеренно делаю шаг десериализации в другой класс, чтобы имитировать старое приложение, пытающееся загрузить новый формат):

// --- 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, чтобы использовать отдельное свойство и оставить значения перечисления неизмененными, но я хотел бы сделать перечисления обратно совместимыми, если это возможно.

4 ответов


поскольку v1 открыт, есть ли некоторые метаданные, которые я могу использовать при сериализации в v2, чтобы избежать этой проблемы? Я могу, конечно, выйти из этой проблемы, переписав v2, чтобы использовать отдельное свойство и оставить значения перечисления неизмененными, но я хотел бы сделать перечисления обратно совместимыми, если это возможно.

то, что вы испытываете-это protobuf-чистая ошибка, описанная здесь protobuf-net-issue #422: недопустимое поведение при десериализации неизвестные значения enum.

кажется, что он еще не исправлен в соответствии с here protobuf-net неисправное исключение перечисления (выпуск 422) нужен хороший обходной путь (и, конечно, ваш пост).

к сожалению, вам нужно либо исправить protobuf-net исходный код или использовать указанные решения.

обновление: Я проверил код в GitHub репозиторий и подтверждение того, что проблема все еще не исправлена. Вот проблемный код внутри EnumSerializer.cs (the 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
}

добавлять [ProtoContract(EnumPassthru=true)] к вашим перечислениям позволит protobuf-net десериализовать неизвестные значения.

к сожалению, нет способа ретроактивно исправить ваш v1. Вам придется использовать другую собственность.


вашему 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; }
}

тем не менее это не решает проблему наличия не-вперед-совместимого класса в производстве.


вы можете добавить атрибут DefaultValue к свойству члена proto.

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

уяснить свойства должны быть инициализированы по умолчанию.