4

I have an application that serializes data using BinaryFormatter. A member was added to the class that was serialized from one version to the next without changing the class name. Code was added to handle the possible absence of the added member in old serialized files:

private void readData(FileStream fs, SymmetricAlgorithm dataKey)
{
    CryptoStream cs = null;

    try
    {
        cs = new CryptoStream(fs, dataKey.CreateDecryptor(),
            CryptoStreamMode.Read);
        BinaryFormatter bf = new BinaryFormatter();

        string string1 = (string)bf.Deserialize(cs);
        // do stuff with string1

        bool bool1 = (bool)bf.Deserialize(cs);
        // do stuff with bool1

        ushort ushort1 = (ushort)bf.Deserialize(cs);
        // do stuff with ushort1

        // etc. etc. ...

        // this field was added later, so it may not be present
        // in the serialized binary data.  Check for it, and if
        // it's not there, do some default behavior

        NewStuffIncludedRecently newStuff = null;

        try
        {
            newStuff = (NewStuffIncludedRecently)bf.Deserialize(cs);
        }
        catch
        {
            newStuff = null;
        }

        _newStuff = newStuff != null ?
                new NewStuffIncludedRecently(newStuff) :
                new NewStuffIncludedRecently();
    }
    catch (Exception e)
    {
        // ...
    }
    finally
    {
        // ...
    }
}

The point I'm at now is that I'd really like to just rinse and repeat with another member I'd like to add, which would mean I'd add another field and try-catch block similar to that for NewStuffIncludedRecently.

I had thought of just making the entire class [Serializable] but wouldn't that break compatibility with the old serialized data?

My main concern is that I'm not clear how the deserialization works. If I add in handling for another optional field similarly to above, will it work? What are other options I have for handling these changes better?

Thanks in advance as always.

John
  • 15,990
  • 10
  • 70
  • 110
  • Serializing field by field with binaryformatter feels like having an extreme amount of overhead in size. You should just use a binarywriter if you want to do field by field serialization. I recommend keeping the legacy serialization scheme for versions older thsn version X, and passing any newer versionsn to a scheme based on DataContracts and/or protocol buffers for example. – Anders Forsgren Nov 23 '11 at 16:22
  • Indeed; BinaryFormatter can be a huge pain when versioning; contract based serializers tend to be much friendlier (XmlSerializer, DataContractSerializer, protobuf-net, etc). – Marc Gravell Nov 23 '11 at 16:29

1 Answers1

4

If you mark the new fields with [OptionalField] it should work, but I have heard reports of flakiness in some cases. I can't say for sure, since I avoid BinaryFormatter, because it has so many issues when versioning :) (plus, it isn't as "tight" as some alternatives, and has severe issues if you want to go cross-platform, or to CF/SL etc)

If you are implementing ISerializable, you might try:

foreach(SerializationEntry entry in info) {
    switch(entry.Name) {
         case "Name": Name = (string)info.Value;
         case "Id": Id = (int)info.Value;
         ...
    }
}

But again, must stress - this is doing things the hard way :p With this approach, you only process the data that is actually there.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks for your answer Marc (+1). One thing that wasn't too clear in my question is that the class itself currently does not have [Serializable]. The individual things being serialized are marked [Serializable] (like for example NewStuffIncludedRecently) but the containing class is not. Will I break backwards compatibility if I make the containing class [Serializable]? Wouldn't that add stuff to the object graph? – John Nov 23 '11 at 16:42
  • @John if it implements ISerializable, then the other attributes are essentially ignored, and it is entirely up to you to deserialize it, checking for existence. If it is neither ISerializable nor [Serializable], it wouldn't work, AFAIK. You can enumerate the `info` to see what fields it contains; let me know if you want an example. – Marc Gravell Nov 23 '11 at 16:50
  • @John I added a SerializationEntry example – Marc Gravell Nov 23 '11 at 16:56