¿Cuáles son las deficiencias de la serialización .NET basada en BinaryFormatter incorporada?

¿Cuáles son las deficiencias de la serialización .NET basada en BinaryFormatter incorporada ? (Rendimiento, flexibilidad, restricciones)

Por favor, acompañe su respuesta con algún código si es posible.

Ejemplo:

Los objetos personalizados que se serializan deben decorarse con el atributo [Serializable] o implementar la interfaz ISerializable.

Ejemplo menos obvio:

Los tipos anónimos no se pueden serializar.

Si te refieres a BinaryFormatter :

  • al estar basado en campos, es muy intolerante a la versión; cambiar los detalles de implementación privados y se rompe (incluso simplemente cambiándolo a una propiedad implementada automáticamente )
  • no es compatible cruzado con otras plataformas
  • no es muy amigable con nuevos campos
  • es específico para el ensamblaje (los metadatos están quemados)
  • es específico de MS / .NET (y posiblemente sea específico de la versión .NET)
  • no es seguro de ofuscación
  • no es especialmente rápido, o pequeña producción
  • no funciona en marcos ligeros (CF? / Silverlight)
  • tiene la deprimente costumbre de incorporar cosas que no esperabas (generalmente a través de event )

Pasé mucho tiempo en esta área, incluida la redacción de una implementación (gratuita) de la API de serialización de “búferes de protocolo” de Google para .NET; protobuf-net

Esto es:

  • menor producción y más rápido
  • cross-compatible con otras implementaciones
  • extensible
  • basado en contrato
  • ofuscación segura
  • assembly independiente
  • es un estándar abierto documentado
  • funciona en todas las versiones de .NET (advertencia: no probado en Micro Framework)
  • tiene ganchos para enchufar a ISerializable (para remotos, etc.) y WCF

Dado cualquier objeto aleatorio, es muy difícil probar si realmente es serializable .

El control de versiones de los datos se maneja a través de atributos. Si no está preocupado por el control de versiones, no hay problema. Si es así, es un gran problema.

El problema con el esquema de atributos es que funciona bastante bien para muchos casos triviales (como agregar una nueva propiedad) pero se descompone bastante rápido cuando intentas hacer algo como reemplazar dos valores enum con un valor enum diferente (o cualquier número de escenarios comunes que viene con datos persistentes de larga duración).

Podría entrar en muchos detalles que describen los problemas. Al final, escribir su propio serializador es bastante fácil si necesita …

Si cambia el objeto que está serializando, todos los datos antiguos que ha serializado y almacenado están rotos. Si almacenó en una base de datos o incluso en XML, es más fácil convertir datos viejos a nuevos.

No se garantiza que pueda serializar objetos entre Frameworks diferentes (Say 1.0, 1.1, 3.5) o incluso diferentes Implementaciones CLR (Mono), de nuevo, XML es mejor para este propósito.

Otro problema que me vino a la mente:

Las clases de XmlSerializer se encuentran en un lugar completamente diferente de los formateadores de tiempo de ejecución generics. Y aunque son muy similares a su uso, el XmlSerializer no implementa la interfaz IFormatter. No puede tener un código que le permita simplemente intercambiar el formateador de serialización en tiempo de ejecución entre BinaryFormatter, XmlSerializer o un formateador personalizado sin saltarse algunos aros adicionales.

Los tipos que se serializan deben decorarse con el atributo [Serializable].

Si te refieres a variables en una clase, estás equivocado. Las variables / propiedades públicas se serializan automáticamente

Un poco menos obvio es que el rendimiento es bastante pobre para la serialización de Objetos.

Ejemplo

Tiempo para serializar y deserializar 100.000 objetos en mi máquina:

 Time Elapsed 3 ms Full Serialization Cycle: BinaryFormatter Int[100000] Time Elapsed 1246 ms Full Serialization Cycle: BinaryFormatter NumberObject[100000] Time Elapsed 54 ms Full Serialization Cycle: Manual NumberObject[100000] 

En este sencillo ejemplo, serializar un objeto con un solo campo Int toma 20 veces más lento que hacerlo a mano. De acuerdo, hay algún tipo de información en la secuencia serializada. Pero eso apenas explica la ralentización de 20X.

Estoy de acuerdo con la última respuesta. El rendimiento es bastante pobre. Recientemente, mi equipo de progtwigdores terminó de convertir una simulación de C ++ estándar a C ++ / CLI. Bajo C ++ teníamos un mecanismo de persistencia escrito a mano, que funcionaba razonablemente bien. Decidimos usar el mecanismo de serialización, en lugar de volver a escribir el viejo mecanismo de persistencia.
La antigua simulación con una huella de memoria entre 1/2 y 1 Gig y la mayoría de los objetos que tienen punteros a otros objetos y miles de objetos en tiempo de ejecución persistirían en un archivo binario de aproximadamente 10 a 15 Meg en menos de un minuto. La restauración desde el archivo fue comparable.
Usando los mismos archivos de datos (corriendo uno al lado del otro), el rendimiento de ejecución de C ++ / CLI es aproximadamente el doble de C ++, hasta que hacemos la persistencia (serialización en la nueva versión). La eliminación tarda entre 3 y 5 minutos, leyendo en tomas entre 10 y 20. El tamaño del archivo de los archivos serializados es aproximadamente 5 veces el tamaño de los archivos antiguos. Básicamente, vemos un aumento de 19 veces en el tiempo de lectura, y un aumento de 5 veces en el tiempo de escritura. Esto es inaceptable y estamos buscando formas de corregir esto.

Al examinar los archivos binarios, descubrí algunas cosas: 1. El tipo y los datos de ensamblaje están escritos en texto claro para todos los tipos. Esto es ineficiente desde el punto de vista del espacio. 2. Cada objeto / instancia de cada tipo tiene la información de tipo / ensamblaje hinchado por escrito. Una cosa que hicimos en nuestra mano persigue mechansim fue escribir una tabla de tipos conocida. Cuando descubrimos los tipos por escrito, buscamos su existencia en esta tabla. Si no existiera, se creó una entrada con la información de tipo y un índice asignado. Luego pasamos el tipo infor como un entero. (tipo, datos, tipo, datos) Este ‘truco’ reduciría tremendamente el tamaño. Esto puede requerir el repaso de los datos dos veces; sin embargo, podría desarrollarse un proceso ‘sobre la marcha’, donde, además de agregarlo a la tabla, avanzar a la stream, si pudiéramos garantizar el orden de resituación de la transmisión. .

Tenía la esperanza de volver a implementar algunas de las serializaciones principales para optimizarlo de esta manera, pero, ¡ay !, ¡las clases están selladas! Todavía podemos encontrar una manera de improvisarlo.

Otra situación hace que BinaryFormatter arroje una excepción.

 [Serializable] class SerializeMe { public List _dataList; public string _name; } [Serializable] class Data { public int _t; } 

Imagine que SerializeMe se serializa hoy. Mañana decidimos que ya no necesitamos Datos de clase y lo eliminamos. En consecuencia, modificamos la clase SerializeMe para eliminar la Lista. Ahora es imposible deserializar la versión anterior de un objeto SerializeMe.

La solución es crear una BinaryFormatter personalizada para ignorar las clases adicionales o mantener los datos de la clase con una definición vacía (no es necesario mantener el miembro List).