2017-03-04 1 views
2

J'ai cette déclaration de structure de données:protobuf-net AddField ne tient pas compte 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(); } 
} 

Je me inscris manuellement champ MetaType par le code suivant:

MetaType meta = RuntimeTypeModel.Default.Add(typeof(NotACollectionHolder), false); 
ValueMember member = meta.AddField(1, "some_objects", itemType: null, defaultType: null); 
string proto = Serializer.GetProto<NotACollectionHolder>(); 

Je marque NotACollection avec IgnoreListHandling. J'essaye de forcer AddField à ignorer le fait que NotACollection ressemble à la collection en fournissant itemType: null, defaultType: null.

Néanmoins, j'ai member.ItemType n'est pas nulle, et member.DefaultType n'est pas null non plus. Et some_objects est devenu un champ repeated Generata proto:

message NotACollectionHolder { 
    repeated int32 some_objects = 1; 
} 

Je me attends proto à ressembler à ceci:

message NotACollection { 
    optional int32 some_data = 1 [default = 0]; 
} 
message NotACollectionHolder { 
    optional NotACollection some_objects = 1; 
} 

Comment puis-je y parvenir? Qu'est-ce que je fais mal? Comment puis-je forcer protobuf-net à traiter ce champ comme un champ de non-collection?

Merci d'avance.

Répondre

0

J'ai découvert qu'il s'agit bien d'un bug dans protobuf-net. Première façon de le corriger est de modifier le code source de protobuf-net: dans MetaType.cs de fichiers en fonction

private ValueMember AddField(int fieldNumber, string memberName, Type itemType, Type defaultType, object defaultValue) 

remplacer la ligne

ResolveListTypes(model, miType, ref itemType, ref defaultType); 

avec

if (model.FindWithoutAdd(miType)?.IgnoreListHandling == true) 
{ 
    itemType = null; 
    defaultType = null; 
} 
else 
    ResolveListTypes(model, miType, ref itemType, ref defaultType); 

Une autre façon est d'utiliser la réflexion pour définir les champs privés itemType et defaultType de l'objet ValueMember à null après l'addition du champ:

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); 

Espérons que cela aidera quelqu'un.

0

Je pense que cela peut être un bug ou une limitation avec l'API RuntimeTypeModel.

La méthode qui détermine si ValueMember est une collection est RuntimeTypeModel.ResolveListTypes(). Il essaie d'inférer le type d'élément de collection lorsque le itemType entrant est nul. Lors de la construction d'un contrat pour NotACollectionHolder en utilisant uniquement des attributs statiques, par exemple en faisant:

var model = TypeModel.Create(); 
var schema = model.GetSchema(typeof(NotACollectionHolder)); 

Alors ProtoBuf.Meta.MetaType.ApplyDefaultBehaviour(bool isEnum, ProtoMemberAttribute normalizedAttribute) est appelé à créer une initialiser le ValueMember. Il a la logique suivante:

 // 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; 
      } 
     } 

Notez la vérification explicite IgnoreListHandling? Cela empêche correctement some_objects d'être sérialisé en tant que collection.

À l'inverse, si l'ajoute ValueMember programme comme suit:

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)); 

Alors MetaType.AddField(int fieldNumber, string memberName, Type itemType, Type defaultType, object defaultValue) est appelée, qui fait simplement:

 ResolveListTypes(model, miType, ref itemType, ref defaultType); 

avis il n'y a pas de contrôle pour IgnoreListHandling? C'est la cause de votre problème.

Malheureusement, ValueType.itemType est en lecture seule et MetaType[int fieldNumber] est get-only, donc il ne semble pas y avoir une API simple à appeler pour résoudre ce problème. Vous pourriez envisager reporting an issue.

La seule solution que je pourrais trouver est d'introduire un substitut pour le type NotACollectionHolder comme ceci:

[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 }; 
    } 
} 

Et puis faire:

var model = TypeModel.Create(); 
model.Add(typeof(NotACollectionHolder), false).SetSurrogate(typeof(NotACollectionHolderSurrogate)); 

var schema = model.GetSchema(typeof(NotACollectionHolder)); 

Le contrat généré est aussi nécessaire:

message NotACollectionHolderSurrogate { 
    optional NotACollectionSurrogate some_objects = 1; 
} 
message NotACollectionSurrogate { 
    optional int32 some_data = 1 [default = 0]; 
}