2010-03-04 10 views
27

Nous avons des problèmes avec la sérialisation d'une liste vide. ici un code .NET en utilisant CF 2,0protobuf-net: Sérialisation d'une liste vide

//Generating the protobuf-msg 
ProtoBufMessage msg = new ProtoBufMessage(); 
msg.list = new List<AnotherProtobufMessage>(); 
// Serializing and sending throw HTTP-POST 
MemoryStream stream = new MemoryStream(); 
Serializer.Serialize(stream, msg); 
byte[] bytes = stream.ToArray(); 
HttpWebRequest request = createRequest(); 
request.ContentLength = bytes.Length ; 

using (Stream httpStream = request.GetRequestStream()) 
{    
     httpStream.Write(bytes, 0, bytes.Length); 
} 

nous avons eu une exception, quand nous essayons d'écrire sur le flux (bytes.length hors de portée). Mais un type avec une liste vide ne devrait pas être 0 octets, à droite (type-information?)?

Nous avons besoin de ce type d'envoi, car dans la réponse sont les messages du serveur pour notre client.

Répondre

31

Le format de fil (défini par google - pas dans mon contrôle!) Envoie seulement des données pour articles. Il ne fait aucune distinction entre une liste vide et une liste null. Donc s'il n'y a pas de données à envoyer - oui, la longueur est 0 (c'est un format très frugal ;-p).

Les tampons de protocole n'incluent aucune métadonnée de type sur le fil. Un autre point commun ici est que vous pouvez supposer que votre propriété de liste est automatiquement instanciée comme étant vide, mais ce ne sera pas le cas (à moins que votre code ne le fasse, peut-être dans un initialiseur de champ ou un constructeur).

est ici un hack pratique:

[ProtoContract] 
class SomeType { 

    [ProtoMember(1)] 
    public List<SomeOtherType> Items {get;set;} 

    [DefaultValue(false), ProtoMember(2)] 
    private bool IsEmptyList { 
     get { return Items != null && Items.Count == 0; } 
     set { if(value) {Items = new List<SomeOtherType>();}} 
    } 
} 

Hacky peut-être, mais il devrait fonctionner. Vous pouvez également perdre la Items "set" si vous voulez et juste laisser tomber le bool:

[ProtoMember(1)] 
    public List<SomeOtherType> Items {get {return items;}} 
    private readonly List<SomeOtherType> items = new List<SomeOtherType>(); 

    [DefaultValue(false), ProtoMember(2)] 
    private bool IsEmptyList { 
     get { return items.Count == 0; } 
     set { } 
    } 
0
public List<NotificationAddress> BccAddresses { get; set; } 

vous pouvez remplacer par:

private List<NotificationAddress> _BccAddresses; 
public List<NotificationAddress> BccAddresses { 
    get { return _BccAddresses; } 
    set { _BccAddresses = (value != null && value.length) ? value : null; } 
} 
1

Comme @Marc dit, le format de fil n'envoie données pour les éléments, donc afin de savoir si la liste était vide ou nulle, vous devez ajouter ce bit d'information dans le flux.
Ajout d'un bien supplémentaire pour indiquer si la collection originale était vide ou non est facile, mais si vous ne voulez pas modifier la définition du type d'origine, vous avez une autre deux options:

sérialisation En utilisant Surrogate

Le type de substitution aura la propriété supplémentaire (gardant votre type d'origine intact) et restaurera l'état d'origine de la liste: null, avec des éléments ou vide.

[TestMethod] 
    public void SerializeEmptyCollectionUsingSurrogate_RemainEmpty() 
    { 
     var instance = new SomeType { Items = new List<int>() }; 

     // set the surrogate 
     RuntimeTypeModel.Default.Add(typeof(SomeType), true).SetSurrogate(typeof(SomeTypeSurrogate)); 

     // serialize-deserialize using cloning 
     var clone = Serializer.DeepClone(instance); 

     // clone is not null and empty 
     Assert.IsNotNull(clone.Items); 
     Assert.AreEqual(0, clone.Items.Count); 
    } 

    [ProtoContract] 
    public class SomeType 
    { 
     [ProtoMember(1)] 
     public List<int> Items { get; set; } 
    } 

    [ProtoContract] 
    public class SomeTypeSurrogate 
    { 
     [ProtoMember(1)] 
     public List<int> Items { get; set; } 

     [ProtoMember(2)] 
     public bool ItemsIsEmpty { get; set; } 

     public static implicit operator SomeTypeSurrogate(SomeType value) 
     { 
      return value != null 
       ? new SomeTypeSurrogate { Items = value.Items, ItemsIsEmpty = value.Items != null && value.Items.Count == 0 } 
       : null; 
     } 

     public static implicit operator SomeType(SomeTypeSurrogate value) 
     { 
      return value != null 
       ? new SomeType { Items = value.ItemsIsEmpty ? new List<int>() : value.Items } 
       : null; 
     } 
    } 


Faites vos types Extensible

protobuf-net suggère l'interface IExtensible qui vous permettent d'étendre les types de sorte que les champs peuvent être ajoutés à un message sans rien casser (en savoir plus here) . Pour utiliser l'extension protobuf-net, vous pouvez hériter de la classe Extensible ou implémenter l'interface IExtensible pour éviter les contraintes d'héritage.
Maintenant que votre type est "extensible", vous définissez les méthodes [OnSerializing] et [OnDeserialized] pour ajouter les nouveaux indicateurs qui seront sérialisés au flux et désérialisés lors de la reconstruction de l'objet avec son état d'origine. Les avantages sont que vous n'avez pas besoin de définir de nouvelles propriétés ni de nouveaux types comme substituts, par contre IExtensible n'est pas supporté si votre type a des sous-types définis dans votre modèle de type.

[TestMethod] 
    public void SerializeEmptyCollectionInExtensibleType_RemainEmpty() 
    { 
     var instance = new Store { Products = new List<string>() }; 

     // serialize-deserialize using cloning 
     var clone = Serializer.DeepClone(instance); 

     // clone is not null and empty 
     Assert.IsNotNull(clone.Products); 
     Assert.AreEqual(0, clone.Products.Count); 
    } 

    [ProtoContract] 
    public class Store : Extensible 
    { 
     [ProtoMember(1)] 
     public List<string> Products { get; set; } 

     [OnSerializing] 
     public void OnDeserializing() 
     { 
      var productsListIsEmpty = this.Products != null && this.Products.Count == 0; 
      Extensible.AppendValue(this, 101, productsListIsEmpty); 
     } 

     [OnDeserialized] 
     public void OnDeserialized() 
     { 
      var productsListIsEmpty = Extensible.GetValue<bool>(this, 101); 
      if (productsListIsEmpty) 
       this.Products = new List<string>(); 
     } 
    }