2010-08-27 10 views
8

Utilizamos BinaryFormatter en un juego de C#, para ahorrar el progreso del juego del usuario, los niveles de juego, etc. Nos encontramos con el problema de la compatibilidad con versiones anteriores.Compatibilidad con versiones anteriores en .NET con BinaryFormatter

Los objetivos:

  • diseñador de niveles crea la campaña (niveles & reglas), que cambian el código, la campaña debería funcionar bien. Esto puede suceder todos los días durante el desarrollo antes del lanzamiento.
  • Usuario guarda juego, lanzamos un juego de parche, el usuario todavía debería poder cargar el juego
  • El proceso invisible de conversión de datos debería funcionar sin importar cuán distantes sean las dos versiones. Por ejemplo, un usuario puede omitir nuestras primeras 5 actualizaciones menores y obtener el 6to directamente. Aún así, sus juegos guardados deberían cargar bien.

La solución debe ser completamente invisible para los usuarios y diseñadores de niveles, y como mínimo para los codificadores que desean cambiar algo (por ejemplo, renombrar un campo porque piensan en un nombre mejor).

Algunos gráficos de objetos que serializamos están enraizados en una clase, algunos en otros. La compatibilidad hacia delante no es necesaria.

Potencialmente rompiendo cambios (y lo que sucede cuando serializar la versión antigua y deserializar en la nueva):

  • campo para sumar (consigue default-inicializado)
  • tipo de campo cambio (fallo)
  • cambiar el nombre del campo (equivalente a eliminarlo y agregar uno nuevo)
  • cambiar la propiedad al campo y volver (equivalente a un cambio de nombre)
  • cambiar la propiedad autoejecutada para usar el campo de respaldo (equ ivalente a un cambio de nombre)
  • agregar superclase (equivalente a agregar sus campos a la clase actual)
  • interpretar un campo de manera diferente (p. fue en grados, ahora en radianes)
  • para este tipo de aplicación ISerializable podemos cambiar nuestra implementación de los métodos ISerializable (por ejemplo, empezar a utilizar la compresión dentro de la aplicación ISerializable para algún tipo realmente grande)
  • Cambiar el nombre de una clase, cambiar el nombre de un valor de enumeración

he leído acerca de:

Mi solución actual:

  • Hacemos tantos cambios como sea posible de no separación, mediante el uso de cosas como la devolución de llamada OnDeserializing.
  • Programamos los cambios de interrupción una vez cada 2 semanas, por lo que hay menos código de compatibilidad para mantener.
  • Cada vez que hacemos un cambio de rotura, copiamos todas las las clases [Serializable] que usamos, en un espacio de nombres/carpeta llamado OldClassVersions.VersionX (donde X es el siguiente número ordinal después del último). Hacemos esto incluso si no vamos a hacer un lanzamiento pronto.
  • Al escribir en un archivo, lo que serializamos es una instancia de esta clase: clase SaveFileData {int version; datos de objetos; }
  • Cuando al leer el archivo, que deserializar el SaveFileData y pasarlo a una rutina iterativa de "actualización" que hace algo como esto:

.

for(int i = loadedData.version; i < CurrentVersion; i++) 
{ 
    // Update() takes an instance of OldVersions.VersionX.TheClass 
    // and returns an instance of OldVersions.VersionXPlus1.TheClass 
    loadedData.data = Update(loadedData.data, i); 
} 
  • Para mayor comodidad, la función Update(), en su aplicación, se puede utilizar una función CopyOverlappingPart() que utiliza la reflexión para copiar tantos datos como sea posible a partir de la versión anterior a la nueva versión. De esta forma, la función Update() solo puede manejar cosas que realmente cambiaron.

Algunos problemas con eso:

  • la deserializer Deserializa con la clase Foo en lugar de a la clase OldClassVersions.Version5.Foo - porque la clase Foo es lo que fue serializada.
  • casi imposible probar o depurar
  • requiere para mantener copias en todo viejos de un montón de clases, que es propenso a errores, frágil y molesto
  • no sé qué hacer cuando queremos cambiar el nombre de una clase

Esto debería ser un problema muy común. ¿Cómo la gente suele resolverlo?

+0

¿Decidió cambiar a la serialización xml o encontró una forma mejor de hacerlo? La serialización de XML tiene limitaciones que no funcionarán para mi programa, así que estoy planeando seguir su método con algunas de las adiciones de K.Hoffmann. – i8abug

+0

@ i8abug: Cambié a la serialización XML, sí. Si me dices las limitaciones que te preocupan, podría decirte una forma de evitarlas, si es que conozco una. –

+0

¡Gracias! Para empezar, necesito serializar miembros privados y protegidos y diccionarios genéricos. Traté de ver la serialización de DataContract, pero necesito serializar las clases heredadas desconocidas (escritas por otros desarrolladores) y esto no es posible con DataContracts. La serialización binaria parece ser lo único que funciona. También revisé protobuf-net, pero también encontré algunas limitaciones. Cuando cambiaste a la serialización XML, ¿cómo manejaste el verisioning? ¿Creaste un intérprete específico para cada versión de tu XML o hiciste algo como lo has descrito anteriormente? – i8abug

Respuesta

3

Tough one. Me gustaría volcar binario y utilizar la serialización XML (más fácil de administrar, tolerante a los cambios que no son demasiado extremos, como agregar/eliminar campos). En casos más extremos, es más fácil escribir una transformación (tal vez xslt) de una versión a otra y mantener las clases limpias. Si la opacidad y la huella de disco pequeña son un requisito, puede intentar comprimir los datos antes de escribir en el disco.

+0

La función BinarySerialization * es * tolerante a la versión para pequeños cambios, incluidos la adición/eliminación de campos. ¿Qué quiere decir exactamente con "más fácil de administrar"? XSLT suena como una gran solución, en realidad. Y no, el tamaño y el rendimiento del archivo no son un problema. –

+0

por "más fácil de administrar" me refiero a la naturaleza humana legible del formato XML y al desacoplamiento de la representación de datos de la estructura de clases (puede controlar cómo se ve el XML serializado a través de los atributos y la estructura no necesita reflejar la clase real) –

2

Tenemos el mismo problema en nuestra aplicación con el almacenamiento de datos de perfil de usuario (disposición de columna de cuadrícula, configuración de filtro ...).

En nuestro caso, el problema fue el AssemblyVersion.

Para este problema se crea un SerializationBinder que lee la versión actual de montaje de los conjuntos (todos los conjuntos de obtener un nuevo número de versión de nueva implantación) con Assembly.GetExecutingAssembly().GetName().Version.

En el método modificado BindToType, la información de tipo se crea con la nueva versión de ensamblaje.

La deserialización se implementa 'a mano', que significa

  • Deserialize través BinaryFormatter normales
  • obtener todos los campos que tienen que ser deserialized (anotado con el propio atributo)
  • objeto de relleno con los datos de el objeto deserializado

Funciona con todos nuestros datos y desde tres o cuatro versiones.

Cuestiones relacionadas