views:

146

answers:

6

I noticed the XmlSerializer is more forgiving to adding new members, removing existing ones, etc to the serialized types.

When I did this with the BinaryFormatter, and tried to deserialize the old data, it threw an exception.

What other alternatives are there for forgiving options, i.e. one that doesn't throw an exception just uses default values, skips them, etc?

Are protocol buffers forgiving in this regard?

+2  A: 

You could inherit your class from ISerializable and define a custom GetObjectData. I haven't tested this, but such a class might be deserializable from a binary format, even if changes have since been made to the class.

EDIT

I just confirmed that this works. You can use code like the example below to explicitly define how an object is serialized and deserialized. It would then be up to you to make these methods work with older versions of your class. I tested this by serializing an instance of Cereal to a binary file, then making changes to the class and reading the file back in for deserialization.

[Serializable]
private class Cereal : ISerializable
{
    public int Id { get; set; }
    public string Name { get; set; }

    public Cereal()
    {
    }

    protected Cereal( SerializationInfo info, StreamingContext context)
    {
     Id = info.GetInt32 ( "Id" );
     Name = info.GetString ( "Name" );
    }

    public void GetObjectData( SerializationInfo info, StreamingContext context )
    {
     info.AddValue ( "Id", Id );
     info.AddValue ( "Name", Name );
    }
}
MikeWyatt
Yes, this is precisely is the reason that ISerializable exists - so you can deal with previous formats.
Martin Clarke
A: 

I strongly recommend doing your own serialization so that you have well-defined file formats independent of the language schemes.

Joshua
+4  A: 

You mention binary, and indeed BinaryFormatter is very brittle here. The problem is that BinaryFormatter is type and field based. Instead, you want a contract-based serializer, such as XmlSerialzier, DataContractSerializer (3.0), etc.

Or for binary, protobuf-net is a C# implementation of Google's "protocol buffers" wire format, but re-implemented along .NET lines; (note: I'm the author...).

It is (like the others) data-contract based, but instead of <CustomerName>asdasd</CustomerName> etc, it uses numeric tags to identify things instead; so:

[ProtoContract]
public class Customer {
    [ProtoMember(1)]
    public string Name {get;set;}

    // ...
}

As you add more members you give them new unique numbers; this keeps it extensible without relying on any names etc. Plus it is very fast ;-p As with XmlSerializer, it will ignore things it doesn't expect (or it can store them for safe round-trip of unexpected data), and supports the same default things. You can even use your existing xml attributes:

[XmlType]
public class Customer {
    [XmlElement(Order=1)]
    public string Name {get;set;}

    // ...
}

I could talk about this subject all day, so I'd better shut up before [too late].

Marc Gravell
Thanks Marc, please feel free to talk more :) One thing I wonder is if say you change the type and added new unique numbers, can you get rid of the old numbers later on? Like if you used 1,2,3,4 and now 5 for a new member that replaces 2. Can you later on get 2 to represent 5? I just thought this might be cleaner for my purpose?
Joan Venge
i've learned two things from this comment. you are British, and quite self assured. :D
Itay
lol, you mean Marc right?
Joan Venge
@Joan - it would be inadvisable to re-use numbers, since then you have ambiguity if you get data of an unknown age... do you use the old contract or the new? As long as you didn't try to use old data it would *work* of course...
Marc Gravell
I think we all want marc to talk as much as he can :)
Stan R.
Self assured? I think I'd see it as "enthusiastic about this topic"...
Marc Gravell
Yes, I only use the new one. Basically this is a personal app where I don't need to maintain backwards compatibility till I release it to the public. So till then if I do change the types, it would be cool if I could condense it before release, so at least it seems cleaner. But how would you do it? Probably would take a couple iterations to use the old numbers, right?
Joan Venge
A: 

I actually find that the binary formatter is the most durable in the long run.

It provides excellent forward compatibility. That is to say, if you upgrade the file to a new version, it will not work with the old deserializer.

I generally create some simple data classes that I want to use for serialization. When i need to change the class, I implement the OnDeserialized / OnDeserializing methods. This allows the data to be upgraded.

The binary formatter does not require that you have a public setter for your properties, which to me is a big problem sometimes.

    [Serializable]
    public class data
    {
      private int m_MyInteger;
      // New field
      private double m_MyDouble;

      [OnDeserializing]
      internal void OnDeserializing(StreamingContext context)
      {
        // some good default value
        m_MyDouble = 5;
      }

      public int MyInteger
      {
        get{ return m_MyInteger; }
        set { m_MyInteger = value; }
      }
   }
Scott P
A: 

I think the following post could help you. I also agree with others who said to write your own serializer. It is way better than generated code from xsd.exe .

See the post below:

http://stackoverflow.com/questions/952264/serialization-and-deserialization-into-an-xml-file-c/1289652#1289652

Tim
A: 

You can also look at the OptionalFieldAttribute for use with SerializableAttribute/NonSerializedAttribute and the BinaryFormatter and SoapFormatter

... version 1

[Serializable]
public class MyClass
{
    public string field1;

    [NonSerialized]
    public string field2;
}

... version 2

[Serializable]
public class MyClass
{        
    public string field1;

    [NonSerialized]
    public string field2;

    [OptionalField]
    public string field3;
}
Matthew Whited