2012-03-27 10 views
5

Estoy escribiendo un serializador/deserializador binario para convertir un número de tipos de objetos en/desde un flujo de bytes. Los objetos representan los comandos API y sus respuestas asociadas para un dispositivo conectado por Bluetooth o USB. Estoy usando el BinaryWriter & BinaryReader para escribir/leer desde/hacia la transmisión.BinaryReader genérico

El serializador es fácil. Las características que se serializarán se etiquetan con un atributo que especifica el orden en el que se escribirán en la secuencia de bytes. Repetir las propiedades utilizando los controles de resolución y resolución de sobrecarga seleccionando el método correcto Write(...) del BinaryWriter.

El deserializador no es tan simple. De nuevo, puedo iterar a través de las propiedades en la clase de respuesta particular que espero determinar los tipos que deben leerse en la secuencia. El truco es escoger el método correcto para llamar al BinaryReader y leer el valor que necesito. He pensado en dos enfoques.

  1. Una declaración de cambio grande que llama al método correcto ReadXXXX() según el tipo que se va a leer.
  2. Use el nombre del tipo que necesito para construir el nombre del método que necesito en una cadena, y luego invoque el método usando relection.

¿Hay una manera más simple en la que no estoy pensando? Es una lástima que no puedas hacer una resolución de sobrecarga en función del tipo de devolución que desees.

Respuesta

1

He utilizado la opción 1 (instrucción de interruptor grande) en un deserializador binario. Un método más limpio podría ser algo como:

{ 
    object result; 

    BinaryReader ...; 
    foreach (var propertyInfo in ...) 
    { 
     Func<BinaryReader, object> deserializer; 
     if (!supportedTypes.TryGetValue(propertyInfo.PropertyType, out deserializer)) 
     { 
      throw new NotSupportedException(string.Format(
       "Type of property '{0}' isn't supported ({1}).", propertyInfo.Name, propertyInfo.PropertyType)); 
     } 

     var deserialized = deserializer(reader); 
     propertyInfo.SetValue(result, deserialized, null); 
    } 
} 

private static Dictionary<Type, Func<BinaryReader, object>> supportedTypes = new Dictionary<Type, Func<BinaryReader, object>> 
{ 
    { typeof(int), br => br.ReadInt32() }, 
    // etc 
}; 

Otra opción es dejar que las clases de comando mismos hacen la serialización:

interface IBinarySerializable 
{ 
    void Serialize(BinaryWriter toStream); 
    void Deserialize(BinaryReader fromStream); 
} 

Luego, en sus comandos:

abstract class Command : IBinarySerializable 
{ 

} 

class SomeCommand : Command 
{ 
    public int Arg1 { get; set; } 

    public void Serialize(BinaryWriter toStream) 
    { 
     toStream.Write(Arg1); 
    } 

    public void Deserialize(BinaryReader fromStream) 
    { 
     Arg1 = fromStream.ReadInt32(); 
    } 
} 

y la serialización genérica métodos:

void Serialize<T>(T obj) where T : IBinarySerializable 
{ 
    obj.Serialize(_stream); 
} 

T Deserialize<T>() where T : new(), IBinarySerializable 
{ 
    var result = new T(); 
    result.Deserialize(_stream); 
    return result; 
} 

Pero de esta manera podría terminar duplicando algún código. (Por otro lado, las clases derivadas pueden llamar a sus versiones de clase primaria de Serialize/Deserialize si eso tiene sentido en su escenario, y eso funciona bien.)

+0

Un par de opciones realmente buenas, gracias. Me estoy inclinando hacia el primero porque encajará mejor con mi código actual. Gracias por tu ayuda. –

0

No sé si entiendo completamente lo que está tratando de hacer . Pero parece que deberás echar un vistazo más de cerca a BinaryFormatter y su serialización se sustituye por .NET. BinaryFormatter le permite serializar y deserializar fácilmente los objetos y los sustitutos de serialización le permiten agregar su serialización personalizada y lógica de deserialización y también hacer posible la reasignación de un objeto a otro.

Tome un vistazo aquí:

BinaryFormatter http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter.aspx

ISerializationSurrogate http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializationsurrogate.aspx

SurrogateSelector http://msdn.microsoft.com/en-us/library/system.runtime.serialization.surrogateselector.aspx

SerializationBinder http://msdn.microsoft.com/en-us/library/system.runtime.serialization.serializationbinder.aspx

También puede ver un pequeño ejemplo aquí, donde tengo un método que puede serializar cualquier objeto a una cadena codificada en base64, y luego un método deserialize que puede deserializar esta cadena. También agrego un SerializationBinder al formateador que reasigna la serialización del tipo MyOldClass al tipo MyNewClass y también agrego un ISerializationSurrogate personalizado que puede procesar los valores de los campos en el objeto antes de que se agregue a la clase ner.

public class SerializeDeserializeExample { 
    public string Serialize(object objectToSerialize) { 
     using(var stream = new MemoryStream()) { 
     new BinaryFormatter().Serialize(stream, objectToSerialize); 
     return Convert.ToBase64String(stream.ToArray()); 
     } 
    } 

    public object Deserialize(string base64String) { 
     using(var stream = new MemoryStream(Convert.FromBase64String(base64String))) { 
     var formatter = new BinaryFormatter(); 
     var surrogateSelector = new SurrogateSelector(); 
     formatter.SurrogateSelector = surrogateSelector; 
     formatter.Binder = new DeserializationBinder(surrogateSelector); 
     return formatter.Deserialize(stream); 
     } 
    } 
} 


public class MyDeserializationBinder : SerializationBinder { 
    private readonly SurrogateSelector surrogateSelector; 

    public MyDeserializationBinder(SurrogateSelector surrogateSelector) { 
     this.surrogateSelector = surrogateSelector; 
    } 

    public override Type BindToType(string assemblyName, string typeName) { 
     if(typeName.Equals("MyOldClass", StringComparison.InvariantCultureIgnoreCase)) { 
     return RemapToType(); 
     } 
     return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName)); 
    } 

    private Type RemapToType() { 
     var remapToType = typeof(MyNewClass); 
     surrogateSelector.AddSurrogate(remapToType, 
           new StreamingContext(StreamingContextStates.All), 
           new MyCustomDeserializationSurrogate()); 
     return remapToType; 
    } 
} 

public sealed class MyCustomDeserializationSurrogate : ISerializationSurrogate { 

    public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context) { 
     throw new NotImplementedException(); 
    } 

    public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { 
     var objectType = obj.GetType(); 
     var fields = GetFields(objectType); 
     foreach(var fieldInfo in fields) { 
     var fieldValue = info.GetValue(fieldInfo.Name, fieldInfo.FieldType); 
     fieldValue = DoSomeProcessing(fieldValue); 
     fieldInfo.SetValue(obj, fieldValue); 
     } 
     return obj; 
    } 

    private static IEnumerable<FieldInfo> GetFields(Type objectType) { 
     return objectType.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | 
          BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 
    } 

    private static object DoSomeProcessing(object value){ 
     //Do some processing with the object 
    } 
} 
+0

Parece una buena opción. Lamentablemente, estas clases no están disponibles en .Net para las aplicaciones Metro Style, que es mi plataforma de destino. Probablemente debería haber agregado una etiqueta para eso a mi pregunta. –