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];
}
Другие вопросы по тегам