2011-11-14 13 views
6

Estoy escribiendo un serializador personalizado para los tipos de estructura para la interoperabilidad con un protocolo que no puedo modificar. Estoy usando el reflejo para extraer los valores de los miembros de la estructura y escribirlos en un BinaryWriter. Está diseñado solo para admitir tipos básicos y matrices de ellos.Fundición dinámica de tipos desconocidos para serialización

if  (fi.FieldType.Name == "Int16") bw.Write((Int16)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt16") bw.Write((UInt16)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Int32") bw.Write((Int32)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt32") bw.Write((UInt32)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Int64") bw.Write((Int64)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt64") bw.Write((UInt64)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Single") bw.Write((float)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Double") bw.Write((double)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Decimal") bw.Write((decimal)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Byte") bw.Write((byte)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "SByte") bw.Write((sbyte)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "String") bw.Write((string)fi.GetValue(obj)); 

Obviamente, esto es feo, y se pone aún más fea cuando quiero hacer lo mismo con las matrices de este tipo también.

Lo que sería realmente agradable es si podría hacer algo como esto:

bw.Write((fi.FieldType) fi.GetValue(obj)); 

Después, realice el mismo tipo de cosas para las matrices.

¿Alguna idea?

+0

+1 para esta pregunta, he estado buscando una forma sólida de hacer esto por bastante tiempo. –

+0

¿Cuál es el tipo de fi? – drdwilcox

+0

Si el código feo termina siendo la única opción, generalmente uso plantillas T4 para este tipo de escenario para evitar errores estúpidos y dejar que Visual Studio genere todo el código automáticamente. Solo necesitarías una lista de tipos para iterar o algo así. – mellamokb

Respuesta

4

Puede utilizar la reflexión para invocar la versión correcta de Write

public static void WriteField(BinaryWriter bw, object obj, FieldInfo fieldInfo) 
{ 
    typeof(BinaryWriter) 
     .GetMethod("Write", new Type[] { fieldInfo.FieldType }) 
     .Invoke(bw, new object[] { fieldInfo.GetValue(obj) }); 
} 
+0

Parece que podría funcionar. Voy a darle una oportunidad. – Polynomial

+0

@Polynomial tenga en cuenta que será mucho más lento ... la reflexión es notoriamente lenta. Dependiendo de su escenario, aún así puede ser ** lo suficientemente rápido **. –

+0

Impresionante. ¡Funciona absolutamente perfecto! : D – Polynomial

2

Este código no es realmente feo ... es simplemente repetitivo. Pero en realidad es bastante limpio, corto y muy fácil de entender. Si tuviera que contar con un millón de tipos diferentes, eso sería una cosa, pero solo hay un número limitado.

Si puede hacer lo que quiere hacer, será difícil de mantener si alguna vez hay un problema o necesita hacer algo más y otro programador puede no entenderlo ... o usted puede haber olvidado qué demonios hizo y tener que volver a aprenderlo.

De esta manera usted tendrá: -Se ha añadido el tiempo de desarrollo adicional legibilidad -reducido velocidad -reducido mantenimiento -aumento

veces nos gusta tener problemas que son demasiado simples y hacerlos más desafiante. Pero a menudo el buen código comercial es simplemente un código mundano y aburrido.

+0

Con respecto a "es simplemente repetitivo", se vuelve mucho más horrible cuando tengo que dar cuenta de 'T []', 'List ', 'Dictionary ', así como la deserialización también. – Polynomial

+0

Entiendo. Creo que la respuesta de Jacob es bastante sencilla ... Elegiría eso sobre la solución vcsJones si quieres tomar esta ruta. –

2

Si quiere simplificarlo, puede usar una expresión para hacer la llamada correcta dinámicamente.

//Cache the generated method for re-use later, say as a static field of dictionary. It shouldn't grow too-big given the number of overloads of Write. 
private static Dictionary<Type, Action<BinaryWriter, object>> _lambdaCache = new Dictionary<Type, Action<BinaryWriter, object>>(); 

//... 

if (!_lambdaCache.ContainsKey(fi.FieldType)) 
{ 
    var binaryWriterParameter = Expression.Parameter(typeof(BinaryWriter)); 
    var valueParameter = Expression.Parameter(typeof(object)); 
    var call = Expression.Call(binaryWriterParameter, "Write", null, Expression.Convert(valueParameter, fi.FieldType)); 
    var lambda = Expression.Lambda<Action<BinaryWriter, object>>(call, binaryWriterParameter, valueParameter).Compile(); 
    _lambdaCache.Add(fi.FieldType, lambda); 
} 
var write = _lambdaCache[fi.FieldType]; 
write(bw, fi.GetValue(obj)); 

Lo que estamos haciendo aquí es generar dinámicamente el código para realizar la llamada que necesita al escritor binario. Esto suena más complicado de lo que es, pero lo que estamos haciendo es crear una expresión para el método "Escribir" de BinaryWriter. También lo lanzamos dinámicamente usando Expression.Convert, por lo que se invoca la sobrecarga correcta de Write. Tomamos dos parámetros de BinaryWriter y valor para escribir. Finalmente, compilamos el lambda y lo guardamos en caché para ese tipo para su reutilización más tarde.

Dependiendo de sus necesidades, esto será mucho más rápido que usar la reflexión sobre BinaryWriter.

+0

Rechazaré esto si agrega más detalles sobre cómo y por qué esto funciona. –

+0

¿Esto tomará en cuenta la sobrecarga de 'Escribir '? Tiene que llamar a 'Write (Int32)' cuando el tipo devuelto de 'fi.GetValue (obj)' es 'Int32', a pesar de que la firma del método de' GetValue' es 'object GetValue (object)'. – Polynomial

+0

Una buena solución, excepto que pasa el valor de campo como una constante y, por lo tanto, debería crear una nueva lambda y compilarla cada vez que serialice cualquier instancia de la clase/estructura. ¿Podría agregar un Expression.Convert para convertir el valor del campo al tipo correcto y luego convertir el valor del campo en un parámetro? Entonces podría llamarlo repetidamente sin tener que volver a compilar. –

2

Hago un código muy similar para protobuf-net; Type.GetTypeCode(...) es una gran ayuda, lo que permite una switch:

switch(Type.GetTypeCode(fi.FieldType)) { 
    case TypeCode.Int16: bw.Write((Int16)fi.GetValue(obj)); break 
    case TypeCode.UInt32: bw.Write((UInt16)fi.GetValue(obj)); break; 
     ... etc lots and lots 
} 

todavía un poco repetitiva, pero sólo un vistazo a la vez Type - el resto es un switch.

Si está utilizando 4.0, otro truco podría ser:

dynamic value = fi.GetValue(obj); 
bw.Write(value); 

la que se tratar de escoger el más adecuado sobrecarga en tiempo de ejecución.Sin embargo, en mi opinión, esto no es motivo suficiente para usar dynamic aquí.

Un pensamiento final sería: usar meta-programación (como ILGenerator) para crear el código en tiempo de ejecución - más compleja, pero más rápido, y no tiene ninguno de estos controles en la ejecución tiempo (justo cuando se prepara el modelo).

1

puedo pensar en tres opciones:

1) BinaryFormatter - esto podría ser capaz de llevar a cabo su tarea de manera muy sencilla, por el Serialize método.
2) Como sugieres, usando la reflexión. Código sería algo como esto:

// sample source data 
object src = (uint)234; 

var bwType = typeof(BinaryWriter); 
var argTypes = new Type[] { src.GetType() }; 
var m = bwType.GetMethod("Write", argTypes); 
var args = new object[] { src }; 
m.Invoke(bw, args); 

3) utilizar una plantilla T4 para generar código rápidamente. El código sigue siendo feo, pero al menos requiere mucho menos mantenimiento. Utilizo este patrón a menudo en algunos de mis proyectos porque es lo mejor de ambos mundos: no se penaliza el rendimiento por la reflexión, sino todos los beneficios del código generado dinámicamente.

0

Incluso si no hace nada más, switch funciona con cadenas, y haría mucho más fácil de leer.

Dada la conversión explícita está trabajando:

Type t = Type.GetType(String.Concat("System.", fi.FieldType.Name)); 

A continuación, utilice

MethodInfo m = typeof(BinaryWriter).GetMethod("Write", new type[] { t }); 

Si no es nula

m.Invoke(bw, new object[] { fi.GetValue(obj) }); 

Esto es suponiendo FieldType.Name corresponde a un tipo que está en su alcance. No dije qué incluiría allí una matriz, pero si es Int16[], es solo un poco de inestabilidad, y puede estar subclasificando BinaryWriter y agregando algunas sobrecargas adicionales para los tipos que el de la caja no trata. Si está haciendo mucho de esto, algún tipo de caché Name, Type y MethodInfo, probablemente sería útil.

Cuestiones relacionadas