Question

Nous avons des problèmes avec le sérialisation d'une liste vide. Ici quelques code dans .NET en utilisant CF 2.0

//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 obtenu une exception, lorsque nous essayons d'écrire sur le flux (Bytes.Length hors de portée). Mais un type avec une liste vide ne devrait pas être de 0 octets, à droite (information de type?)?

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

Était-ce utile?

La solution

Le format de fil (défini par Google - pas dans mon contrôle!) éléments. Il ne fait aucune distinction entre un vide liste et un nul liste. 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 gotcha commun ici est que vous pouvez supposer que votre propriété de liste est automatiquement instanciée comme vide, mais elle ne le sera pas (sauf si votre code le fait, peut-être dans un initialiseur ou un constructeur de champ).

Voici un piratage réalisable:

[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 ça devrait fonctionner. Vous pourriez également perdre le Items "Set" si vous voulez et déposez simplement 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 { }
    }

Autres conseils

Comme l'a dit @MARC, le format de fil envoie uniquement des données pour les éléments, donc pour savoir si la liste était vide ou nul, vous devez ajouter ce peu d'informations au flux.
L'ajout d'une propriété supplémentaire pour indiquer si la collection d'origine était vide ou non est facile, mais si vous ne souhaitez pas modifier la définition de type d'origine, vous avez deux autres options:

Sérialiser à l'aide de substitution

Le type de substitution aura la propriété supplémentaire (garder votre type d'origine intact) et restaurera l'état d'origine de la liste: null, avec des articles 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;
        }
    }


Rendre vos types extensibles

Protobuf-net suggère l'interface iextensible qui vous permet d'étendre les types afin que les champs puissent être ajoutés à un message sans rien (lire la suite ici). Afin d'utiliser une extension Protobuf-Net, vous pouvez hériter du Extensible classe ou implémenter le IExtensible interface pour éviter la contrainte de succession.
Maintenant que votre type est "extensible", vous définissez [OnSerializing] et [OnDeserialized] Méthodes pour ajouter les nouveaux indicateurs qui seront sérialisés au flux et désérialisés à partir de celui-ci 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 en tant que substituts, les inconvénients sont que IExtensible n'est pas pris en charge 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>();
        }
    }
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; }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top