2011-08-19 11 views
10

Supongamos que tengo un tipo de valor inmutable como esto:¿Cómo usar protobuf-net con tipos de valores inmutables?

[Serializable] 
[DataContract] 
public struct MyValueType : ISerializable 
{ 
private readonly int _x; 
private readonly int _z; 

public MyValueType(int x, int z) 
    : this() 
{ 
    _x = x; 
    _z = z; 
} 

// this constructor is used for deserialization 
public MyValueType(SerializationInfo info, StreamingContext text) 
    : this() 
{ 
    _x = info.GetInt32("X"); 
    _z = info.GetInt32("Z"); 
} 

[DataMember(Order = 1)] 
public int X 
{ 
    get { return _x; } 
} 

[DataMember(Order = 2)] 
public int Z 
{ 
    get { return _z; } 
} 

public static bool operator ==(MyValueType a, MyValueType b) 
{ 
    return a.Equals(b); 
} 

public static bool operator !=(MyValueType a, MyValueType b) 
{ 
    return !(a == b); 
} 

public override bool Equals(object other) 
{ 
    if (!(other is MyValueType)) 
    { 
     return false; 
    } 

    return Equals((MyValueType)other); 
} 

public bool Equals(MyValueType other) 
{ 
    return X == other.X && Z == other.Z; 
} 

public override int GetHashCode() 
{ 
    unchecked 
    { 
     return (X * 397)^Z; 
    } 
} 

// this method is called during serialization 
public void GetObjectData(SerializationInfo info, StreamingContext context) 
{ 
    info.AddValue("X", X); 
    info.AddValue("Z", Z); 
} 

public override string ToString() 
{ 
    return string.Format("[{0}, {1}]", X, Z); 
} 
} 

Funciona con BinaryFormatter o DataContractSerializer pero cuando intento usarlo con protobuf-net (http://code.google.com/p/protobuf-net/) serializador me sale este error:

Cannot apply changes to property ConsoleApplication.Program+MyValueType.X

Si aplico setters a las propiedades marcadas con el atributo DataMember funcionará, pero luego rompe la inmutabilidad de este tipo de valor y eso no es deseable para nosotros.

¿Alguien sabe lo que tengo que hacer para que funcione? Me he dado cuenta de que hay una sobrecarga del método ProtoBu.Serializer.Serialize que admite SerializationInfo y StreamingContext, pero no los he usado fuera del contexto de implementación de la interfaz ISerializable, por lo que hay ejemplos de código sobre cómo usarlos en este contexto será muy apreciado!

Gracias,

EDIT: por lo Desenterré algún artículo de MSDN de edad y tengo una mejor comprensión de dónde y cómo se utiliza SerializationInfo y StreamingContext, pero cuando traté de hacer esto:

var serializationInfo = new SerializationInfo(
    typeof(MyValueType), new FormatterConverter()); 
ProtoBuf.Serializer.Serialize(serializationInfo, valueType); 

resulta que el método Serialize<T> solo permite tipos de referencia, ¿hay alguna razón en particular para eso? Parece un poco extraño dado que puedo serializar los tipos de valor expuestos a través de un tipo de referencia.

+0

Lo siento por el retraso - fin de semana agitado –

Respuesta

10

¿Qué versión de protobuf-net estás usando? Si eres la última versión de v2, debería lidiar con esto automáticamente. En caso de que aún no haya implementado este código, actualizaré las áreas de descarga en un momento, pero esencialmente si tu tipo no tiene adornos (sin atributos), detectará el patten "tupla" común que estás usando y decidirá (desde el constructor) que x (parámetro constructor)/X (propiedad) es el campo 1, y z/Z es campo 2.

Otro enfoque es para marcar los campos:

[ProtoMember(1)] 
private readonly int _x; 

[ProtoMember(2)] 
private readonly int _z; 

(o alternativamente [DataMember(Order=n)] en los campos)

que debería w ork, dependiendo del nivel de confianza. Lo que yo no tengo hecho aún es generalizar el código del constructor para los escenarios atribuidos. Eso no es difícil, pero quería impulsar el caso básico primero, luego evolucionarlo.

He añadido los dos siguientes muestras/pruebas with full code here:

[Test] 
    public void RoundTripImmutableTypeAsTuple() 
    { 
     using(var ms = new MemoryStream()) 
     { 
      var val = new MyValueTypeAsTuple(123, 456); 
      Serializer.Serialize(ms, val); 
      ms.Position = 0; 
      var clone = Serializer.Deserialize<MyValueTypeAsTuple>(ms); 
      Assert.AreEqual(123, clone.X); 
      Assert.AreEqual(456, clone.Z); 
     } 
    } 
    [Test] 
    public void RoundTripImmutableTypeViaFields() 
    { 
     using (var ms = new MemoryStream()) 
     { 
      var val = new MyValueTypeViaFields(123, 456); 
      Serializer.Serialize(ms, val); 
      ms.Position = 0; 
      var clone = Serializer.Deserialize<MyValueTypeViaFields>(ms); 
      Assert.AreEqual(123, clone.X); 
      Assert.AreEqual(456, clone.Z); 
     } 
    } 

también:

it turns out that the Serialize method only allows reference types

sí, que era una limitación de diseño que v1 relacionado con el modelo de boxeo, etc; esto ya no se aplica con v2.

Además, tenga en cuenta que protobuf-net no sí consumen ISerializable (aunque puede ser usado para poner en práctica ISerializable).

+0

1 Gran respuesta, casi un post :-) –

+0

Hola Marc, acaba de descargar los nuevos binarios en los que posteaste y funcionó, también he comprobado serializar/deserializar usando una instancia de RuntimeTypeModel y ellos también trabajan – theburningmonk

+0

Pero tengo curiosidad acerca de por qué el método RuntiemTypeModel.Deserialize necesita una instancia para modificar cuando devuelve el objeto modificado de todos modos. En el caso de los tipos de valor inmutables, significa que terminará haciendo algo a lo largo de la línea de: var clone = (MyValueType) FormatterServices.GetUninitializedObject (typeof (MyValueType)); clone = (MyValueType) runtimeTypeModel.Deserialize (memStream, clone, typeof (MyValueType)); que simplemente no se siente del todo bien – theburningmonk

Cuestiones relacionadas