Protobuf-net AddField игнорирует IgnoreListHandling
У меня есть это объявление структуры данных:
[ProtoContract]
public class NotACollectionHolder
{
public NotACollection some_objects;
}
[ProtoContract(IgnoreListHandling = true, ImplicitFields = ImplicitFields.AllPublic)]
public class NotACollection : IEnumerable<int>
{
public int some_data;
// something looks like a collection API
public void Add(int a) { }
public IEnumerator<int> GetEnumerator() { throw new NotImplementedException(); }
IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); }
}
Я вручную регистрирую поле для MetaType
по следующему коду:
MetaType meta = RuntimeTypeModel.Default.Add(typeof(NotACollectionHolder), false);
ValueMember member = meta.AddField(1, "some_objects", itemType: null, defaultType: null);
string proto = Serializer.GetProto<NotACollectionHolder>();
Я отмечаю NotACollection
с IgnoreListHandling
, Я пытаюсь заставить AddField
игнорировать тот факт, что NotACollection
выглядит как коллекция, предоставляя itemType: null, defaultType: null
,
Тем не менее, у меня есть member.ItemType
не является нулевым, и member.DefaultType
также не является нулевым. А также some_objects
стал repeated
поле в сгенерированном proto
:
message NotACollectionHolder {
repeated int32 some_objects = 1;
}
Я жду proto
выглядеть так:
message NotACollection {
optional int32 some_data = 1 [default = 0];
}
message NotACollectionHolder {
optional NotACollection some_objects = 1;
}
Как я могу этого достичь? Что я делаю неправильно? Как я могу заставить protobuf-net обрабатывать это поле как поле, не относящееся к коллекции?
Заранее спасибо.
2 ответа
Я обнаружил, что это действительно ошибка в protobuf-сети. Первый способ исправить это - внести изменения в исходный код protobuf-net: в файле MetaType.cs в функции.
private ValueMember AddField(int fieldNumber, string memberName, Type itemType, Type defaultType, object defaultValue)
заменить линию
ResolveListTypes(model, miType, ref itemType, ref defaultType);
с
if (model.FindWithoutAdd(miType)?.IgnoreListHandling == true)
{
itemType = null;
defaultType = null;
}
else
ResolveListTypes(model, miType, ref itemType, ref defaultType);
Другой способ - использовать отражение для установки приватных полей. itemType
а также defaultType
объекта ValueMember
в null
после добавления поля:
ValueMember m = meta.AddField(++last_field_number, f.Name, f.ItemType, f.DefaultType);
m.GetType().GetField("itemType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(m, null);
m.GetType().GetField("defaultType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(m, null);
Надеюсь, это кому-нибудь поможет.
Я думаю, что это может быть ошибка или ограничение с RuntimeTypeModel
API.
Метод, который определяет, является ли ValueMember
это коллекция RuntimeTypeModel.ResolveListTypes()
, Он пытается определить тип элемента коллекции при поступлении itemType
нулевой. При заключении договора на NotACollectionHolder
используя только статические атрибуты, например, выполнив:
var model = TypeModel.Create();
var schema = model.GetSchema(typeof(NotACollectionHolder));
затем ProtoBuf.Meta.MetaType.ApplyDefaultBehaviour(bool isEnum, ProtoMemberAttribute normalizedAttribute)
вызывается для инициализации ValueMember
, У него следующая логика:
// check for list types
ResolveListTypes(model, effectiveType, ref itemType, ref defaultType);
// but take it back if it is explicitly excluded
if(itemType != null)
{ // looks like a list, but double check for IgnoreListHandling
int idx = model.FindOrAddAuto(effectiveType, false, true, false);
if(idx >= 0 && model[effectiveType].IgnoreListHandling)
{
itemType = null;
defaultType = null;
}
}
Обратите внимание на явную проверку IgnoreListHandling
? Это правильно мешает some_objects
от сериализации как коллекция.
И наоборот, если добавляет ValueMember
программно следующим образом:
var model = TypeModel.Create();
var meta = model.Add(typeof(NotACollectionHolder), false);
var member = meta.AddField(1, "some_objects", null, null);
var schema = model.GetSchema(typeof(NotACollectionHolder));
затем MetaType.AddField(int fieldNumber, string memberName, Type itemType, Type defaultType, object defaultValue)
называется, что просто делает:
ResolveListTypes(model, miType, ref itemType, ref defaultType);
Обратите внимание, что нет проверки для IgnoreListHandling
? Это причина вашей проблемы.
К несчастью, ValueType.itemType
только для чтения и MetaType[int fieldNumber]
только для получения, так что, кажется, нет простого API для вызова, чтобы это исправить. Вы могли бы рассмотреть сообщение о проблеме.
Единственный обходной путь, который я смог найти, - это ввести суррогат для NotACollectionHolder
типа так:
[ProtoContract]
internal class NotACollectionHolderSurrogate
{
[ProtoMember(1)]
internal NotACollectionSurrogate some_objects;
public static implicit operator NotACollectionHolder(NotACollectionHolderSurrogate input)
{
if (input == null)
return null;
return new NotACollectionHolder { some_objects = input.some_objects };
}
public static implicit operator NotACollectionHolderSurrogate(NotACollectionHolder input)
{
if (input == null)
return null;
return new NotACollectionHolderSurrogate { some_objects = input.some_objects };
}
}
[ProtoContract]
internal class NotACollectionSurrogate
{
[ProtoMember(1)]
public int some_data;
public static implicit operator NotACollection(NotACollectionSurrogate input)
{
if (input == null)
return null;
return new NotACollection { some_data = input.some_data };
}
public static implicit operator NotACollectionSurrogate(NotACollection input)
{
if (input == null)
return null;
return new NotACollectionSurrogate { some_data = input.some_data };
}
}
А затем сделайте:
var model = TypeModel.Create();
model.Add(typeof(NotACollectionHolder), false).SetSurrogate(typeof(NotACollectionHolderSurrogate));
var schema = model.GetSchema(typeof(NotACollectionHolder));
Генерируемый контракт соответствует требованиям:
message NotACollectionHolderSurrogate {
optional NotACollectionSurrogate some_objects = 1;
}
message NotACollectionSurrogate {
optional int32 some_data = 1 [default = 0];
}