Обратная совместимость 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; }
}
уяснить свойства должны быть инициализированы по умолчанию.