Managing Object Serialization in MultiCAD.NET



    In a previous article, we talked about the approach that is used to serialize custom objects in the MultiCAD.NET API. Then we talked about the principles of applying this approach to ensure compatibility of object versions and considered the simplest situation when a new version of an object is obtained from the previous one by adding additional fields. Today we bring to your attention an overview of the compatibility process in case of more serious changes, such as deleting, renaming fields or changing their types.


    Consider the situation when in the new version of the object fields are renamed and their types are changed. As an example of a custom object, let us take an object already familiar to us CrossMark:

    image

    The class structure of this object is as follows:

    [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;
    }
    

    Suppose that in the new version of the class, it was required to set the corner points of the label not with points, but with vectors, radiuswhile the field remains unchanged:

    [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, as a result of changes of this kind, objects of the new class will not be compatible with the previous version.
    In order for the new version to "understand" the previous one, it is necessary to implement a mechanism for reading the necessary fields and "transfer" the old data format to the new one. To solve this problem, MultiCAD.NET can use a standard interface ISerializablethat allows for controlled serialization of objects.

    Implementation ISerializablerequires the implementation of two methods:
    • public void GetObjectData(SerializationInfo info, StreamingContext context) - used to serialize the object.
    • public CrossMark(SerializationInfo info, StreamingContext ctx) - constructor used for deserialization.

    For our case, these methods will look as follows:
    // 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);
    }
    

    // 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();
      }
    }
    

    The object now has two constructors: one of them is used when inserting the object into the drawing, the second - when reading the object from the drawing. The deserialization process is divided into two parts: the data from the object of the current version is read in the “normal” mode, and the data of the objects of the previous version are read and converted to the current format in the exception handling section SerializationException, which will be thrown out when trying to deserialize non-existent fields from the previous version.
    If you plan to further develop versions, then an alternative could be to serialize the version of the object into a new field, and separate the deserialization process depending on the value of this field:

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      ...
      info.AddValue("version", 1);
    }
    

    public CrossMark(SerializationInfo info, StreamingContext ctx)
    {
      int version = info.GetInt("version");
      switch (version)
      {
        case 1:
            ...
        case 2:
            ...
        ...    
    }
    


    Thus, we examined the basic cases of serialization of user objects in MultiCAD.NET with various options for changing their structure from version to version. Using the described approaches will allow you to create a flexible mechanism for managing the compatibility of user-defined objects of different versions within your application.
    Generally speaking, the serialization of objects is an extensive and popular topic, so we decided to continue to acquaint you with the implementation of this mechanism in MultiCAD.NET and soon we will bring to your attention a few more articles on this topic. In particular, we will talk about organizing the exchange of object data between applications through serialization in external databases, and also answer other frequently asked questions. And, as always, we are waiting for your comments and interesting topics for discussion.

    Discussion of the article is also available on our forum: forum.nanocad.ru/index.php?showtopic=6516 .
    Translation of the article into English: Managing serialization of custom objects in MultiCAD.NET.

    Also popular now: