ISerializable and backward compatibility
Asked Answered
P

5

6

I have to work an an old application that used binaryFormatter to serialize application data into filestream (say in a file named "data.oldformat") without any optimizazion the main class has been marked with attribute

<serializable()>public MainClass
....... 
end class

and the serialization code

dim b as new binaryformatter
b.serialize(mystream,mymainclass)

In an attempt to optimize the serialization/deserialization process I simply made the class implement the ISerializable interface and wrote some optimized serialization routines

<serializable()>public MainClass
       implements ISerializable
....... 
end class

The optimization works really well but I MUST find a way to reatrive the data inside the old files for backward compatibility.

How can I do that??

Pierluigi

Plastometer answered 10/4, 2010 at 15:34 Comment(0)
S
4

stmax has an excellent answer, however I would implement it like this, which uses SerializationEntry.GetEnumerator() instead of try/catch. This way is cleaner and significantly faster.

public MainClass(SerializationInfo info, StreamingContext context) {
    int version = 0;
    foreach (SerializationEntry s in info)
    {
        if (s.Name == "version") 
        {
            version = (int)s.Value;
            break;
        }
    }

    switch (version) {
      case 0:
        // deserialize "old format"
        break;
      case 1:
        // deserialize "new format, version 1"
        break;
      default:
        throw new NotSupportedException("version " + version + " is not supported.");
    }
}

I would prefer a LINQ version using .FirstOrDefault(), but SerializationInfo does not implement IEnumerable - in face, weirdly enough, it doesn't even implement the old IEnumerable interface.

Sessile answered 28/9, 2011 at 18:47 Comment(0)
F
4

since you've already implemented the ISerializable interface, you've probably also already added the required constructor:

public MainClass(SerializationInfo info, StreamingContext context) {}

you can use the info-object passed to the constructor to retrieve data from the serialized file. by default (i.e. when no ISerializable is implemented), the fields names are used as identifiers during serialization. so if your old class had a field "int x" you can deserialize this using:

this.x = info.GetInt32("x");

for newer versions i normally add a "version" entry during serialization, like this:

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

during deserialization you can check this version entry and deserialize accordingly:

public MainClass(SerializationInfo info, StreamingContext context) {
    int version;
    try {
       version = info.GetInt32("version");
    }
    catch {
       version = 0;
    }

    switch (version) {
      case 0:
        // deserialize "old format"
        break;
      case 1:
        // deserialize "new format, version 1"
        break;
      default:
        throw new NotSupportedException("version " + version + " is not supported.");
    }
}

i haven't compiled that code, might contain typos.

hope that helps.

Fechter answered 10/4, 2010 at 16:3 Comment(4)
great answer stmax! how did u find the default behaviour for the binaryformatter object?Plastometer
i did the following to find the "names" of the entries that were serialized with the default behaviour: foreach (SerializationEntry entry in info.GetEnumerator()) { Trace.WriteLine(entry.Name); }Fechter
Instead of try/catch when getting "version", instead use SerializationInfo.GetEnumerator() and search for the "version" field. It is cleaner and if the field is not found then avoiding throwing the exception makes the entire deserialization (in my testing) 2x faster. SerializationInfo.Get() internally uses the same linear search as doing it yourself with an enumerator, making them both O(n) in the number of fields.Sessile
+1 Good and clean way to preserve backwards compatibility. However, while you are right that field values can be accessed by using the actual field names, this doesn't apply to auto properties. The compiler then uses the format "<PropertyName>k__BackingField". I will add this information to your answer, so you can accept it if you like.Fernald
S
4

stmax has an excellent answer, however I would implement it like this, which uses SerializationEntry.GetEnumerator() instead of try/catch. This way is cleaner and significantly faster.

public MainClass(SerializationInfo info, StreamingContext context) {
    int version = 0;
    foreach (SerializationEntry s in info)
    {
        if (s.Name == "version") 
        {
            version = (int)s.Value;
            break;
        }
    }

    switch (version) {
      case 0:
        // deserialize "old format"
        break;
      case 1:
        // deserialize "new format, version 1"
        break;
      default:
        throw new NotSupportedException("version " + version + " is not supported.");
    }
}

I would prefer a LINQ version using .FirstOrDefault(), but SerializationInfo does not implement IEnumerable - in face, weirdly enough, it doesn't even implement the old IEnumerable interface.

Sessile answered 28/9, 2011 at 18:47 Comment(0)
T
0

Just try the same thing you've been doing so far

BinaryFormatter b = new BinaryFormatter();
MainClass a = b.DeSerialize(mystream) as MainClass;

Implementing ISerializable didn't change your original class, basically you've just added some methods

Talkfest answered 10/4, 2010 at 15:54 Comment(1)
I've added the required constructor (serializationInfo info, streamingContext context) so I can't use b.deserialize without knowing how the main class saved his own data during the default serializationPlastometer
L
0

When serializing your objects add an additional Version field (this shouldn't add too much overhead). Then in your GetObjectData method, try to retrieve the version field first and based on whether this exists or not (by catching the SerializationException) deserialize the old way or the new way. The old way will have just serialized all data so you should just be able to call Get... for all fields.

Landgrabber answered 10/4, 2010 at 15:58 Comment(1)
this is fine for the serialization procedure which uses getObjectData but the problem is during the deserialization procedure which uses a specialized constructor new(info as serializationinfo,context as serializationcontext) the old mainClass version didn't implement ISerializable so I don't know how to retrieve data using the streaminginfo objectPlastometer
W
0

Your previous code should work. Do you get an exception? Try to use new constructor:

 Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
Walkthrough answered 10/4, 2010 at 16:0 Comment(1)
I Had..but It requires to know how the binaryformatter saves data when the class is only marked as serializable...Plastometer

© 2022 - 2024 — McMap. All rights reserved.