Pregunta

Tenemos algunos problemas para serializar una lista vacía. Aquí algún código en .NET usando 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);
}

Tenemos una excepción, cuando intentamos escribir en la transmisión (bytes.length fuera de rango). Pero un tipo con una lista vacía no debe ser 0 bytes, ¿verdad (tipo información?)?

Necesitamos este tipo de envío, porque en la respuesta están los mensajes del servidor para nuestro cliente.

¿Fue útil?

Solución

El formato de cable (definido por Google, ¡no dentro de mi control!) Solo envía datos para elementos. No hace distinción entre un vacío lista y un nulo lista. Entonces, si no hay datos para enviar, sí, la longitud es 0 (es un formato muy frugal; -P).

Los tampones de protocolo no incluyen ningún tipo de metadatos en el cable.

Otra gotcha común aquí es que puede suponer que la propiedad de su lista se instancia automáticamente como vacía, pero no lo estará (a menos que su código lo haga, tal vez en un inicializador o constructor de campo).

Aquí hay un truco viable:

[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 tal vez, pero debería funcionar. También podrías perder el Items "Establecer" si quieres y simplemente suelta el 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 { }
    }

Otros consejos

Como dijo @Marc, el formato de cable solo envía datos para elementos, por lo que para saber si la lista estaba vacía o nula, debe agregar ese poco de información a la transmisión.
Agregar propiedad adicional para indicar si la colección original estaba vacía o no es fácil, pero si no desea modificar la definición de tipo original, tiene otras dos opciones:

Serializar usando sustituto

El tipo sustituto tendrá la propiedad adicional (manteniendo intacto su tipo original) y restaurará el estado original de la lista: nulo, con artículos o vacíos.

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


Haga que sus tipos sean extensibles

ProtoBuf-Net sugiere la interfaz IExtensible que le permite extender los tipos para que los campos se puedan agregar a un mensaje sin que nada se rompa (lea más aquí). Para usar la extensión de ProtoBuf-Net, puede heredar el Extensible clase o implementar el IExtensible interfaz para evitar la restricción de herencia.
Ahora que su tipo es "extensible" que define [OnSerializing] y [OnDeserialized] Métodos para agregar los nuevos indicadores que se serializarán a la corriente y se deserializarán al reconstruir el objeto con su estado original.
Los profesionales es que no necesita definir nuevas propiedades ni tipos nuevos como sustitutos, el contras es que IExtensible no es compatible si su tipo tiene subtipos definidos en su modelo de tipo.

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

Puedes reemplazar con:

private List<NotificationAddress> _BccAddresses;
public List<NotificationAddress> BccAddresses {
   get { return _BccAddresses; }
   set { _BccAddresses = (value != null && value.length) ? value : null; }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top