In the previous post we discussed the approach used for serializing custom objects in the MultiCAD.NET API. We talked about concepts of using that approach for providing object version compatibility and considered the simplest case when a new version of an object is derived from a previous one by adding additional fields of data. Today we are going to discuss the case of deep changes of the object’s structure such as removing, renaming or retyping fields.
Assume that a new version of the object has renamed some fields and changed their types. Let’s take, for example, the CrossMark
object that is already familiar to us from the previous post:
The object of the previous version had the following structure:
1 2 3 4 5 6 7 8 9 10 11 12 |
[CustomEntityAttribute("1C925FA1-842B-49CD-924F-4ABF9717DB62", 2, "Crossmark", "Crossmark Sample Entity")] [Serializable] public class CrossMark : McCustomBase { private Point3d pnt1; private Point3d pnt2; private Point3d pnt3; private Point3d pnt4; private double radius; } |
Let’s assume that in the new version the corner points are required to be defined by vectors (instead of 3d-points), and the radius field remains unchanged:
1 2 3 4 5 6 7 8 9 10 11 12 |
[CustomEntityAttribute("1C925FA1-842B-49CD-924F-4ABF9717DB62", 3, "Crossmark", "Crossmark Sample Entity")] [Serializable] public class CrossMark : McCustomBase { private Vector3d v1; private Vector3d v2; private Vector3d v3; private Vector3d v4; private double radius; } |
Obviously objects of the new version will not be compatible with older objects after such changes.
In order to allow the new version to be able to recognize the older ones, it is required to implement the mechanism for reading desired fields and converting the old data format to the new one. To solve this problem with MultiCAD.NET, the standard ISerializable
interface can be used .
The interface allows an object to control its own serialization and deserialization and requires an implementation of two methods:
- public void GetObjectData(SerializationInfo info, StreamingContext context) — used to serialize the target object,
- public CrossMark(SerializationInfo info, StreamingContext ctx) — the special constructor used to deserialize values.
In our case the methods should be as follows:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Serialization public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("vec1", v1); info.AddValue("vec2", v2); info.AddValue("vec3", v3); info.AddValue("vec4", v4); info.AddValue("radius", radius); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// Deserialization public CrossMark(SerializationInfo info, StreamingContext ctx) { radius = info.GetDouble("radius"); try { v1 = (Vector3d)info.GetValue("vec1", typeof(Vector3d)); v2 = (Vector3d)info.GetValue("vec2", typeof(Vector3d)); v3 = (Vector3d)info.GetValue("vec3", typeof(Vector3d)); v4 = (Vector3d)info.GetValue("vec4", typeof(Vector3d)); } catch (System.Runtime.Serialization.SerializationException) { Point3d pnt1 = (Point3d)info.GetValue("pnt1", typeof(Point3d)); Point3d pnt2 = (Point3d)info.GetValue("pnt2", typeof(Point3d)); Point3d pnt3 = (Point3d)info.GetValue("pnt3", typeof(Point3d)); Point3d pnt4 = (Point3d)info.GetValue("pnt4", typeof(Point3d)); v1 = pnt1.GetAsVector(); v2 = pnt2.GetAsVector(); v3 = pnt3.GetAsVector(); v4 = pnt4.GetAsVector(); } } |
So, now the object has two constructors: one (the default) is used for creating and inserting the object into the drawing, and another – when the object is being read (deserialized) from the drawing.
The deserialization process has been divided into two parts: data from the object of the current version is read regularly, while the data of the previous version is read and converted to the current format inside the catch
block for handling SerializationException
. This exception will be thrown when the application tries to deserialize the nonexistent fields from the previous version.
If you plan to continue developing new versions of the object, then you can also serialize the version number to a new special field and customize the deserialization process depending on this value:
1 2 3 4 5 6 7 |
public void GetObjectData(SerializationInfo info, StreamingContext context) { ... info.AddValue("version", 1); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public CrossMark(SerializationInfo info, StreamingContext ctx) { int version = info.GetInt("version"); switch (version) { case 1: ... case 2: ... ... } |
So we’ve discussed the basic cases of custom object serialization with different types of changes in their structure while moving from one version to another. Using the described approaches, you are able to provide your application with a flexible mechanism for managing objects version compatibility.
Serialization questions are very popular, that is why we will soon continue talking about the implementation of this mechanism in MultiCAD.NET and publish some posts regarding the subject. For example, we will discuss the problem of object data exchange between applications by serializing data to external databases and answer other frequently asked questions.