Serializing a <IMyInterface> List Using ISerializable

Thanks for watching!

I am working on a new version of a product that is deployed in a field. I need to maintain the ability to deserialize outputs from old software.

Here is a contrived example:

I have an existing client base with serialized files that they need access to. For the purposes of this issue, they have a Zoo file with a list of Giraffes in it.

[Serializable] public class Giraffe : ISerializable { public int Age { get; private set; } public Giraffe(int age) { Age = age; } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Age", Age); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] private Giraffe(SerializationInfo info, StreamingContext context) { Age = info.GetInt32("Age"); } } 

Now we are deploying a new version of our Zoo software, and we are going to support anaimals, except for Giraffes, we had to do this for a start, but due to time limitations we had to release 1.0 with a set of functions only for the Giraffe.

 public interface IAnimal { int Age { get; } } [Serializable] public class Animal : IAnimal, ISerializable { public int Age { get; private set; } public Animal (int age) { Age = age; } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Age", Age); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] private Animal(SerializationInfo info, StreamingContext context) { Age = info.GetInt32("Age"); } } 

I use custom serializationBinder so that old Giraffes deserialize as Animals

 public class LegacyZooSerializationBinder : SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { if (typeName.StartsWith("Test.Giraffe")) return typeof(Animal); else if (typeName.StartsWith("System.Collections.Generic.List`1[[Test.Giraffe")) return typeof(List<Animal>); else return null; } } 

The problem is that I don't want my Zoo class to use List as storage, not List. I want to do this for two reasons, for future extensibility, also so that I can make fun of things for unit testing more easily.

Deserializing the new IAnimal list is not a problem. The problem arises when I want to deserialize old style elements. Binder returns the correct type, the correct deserialization constructor is called, everything looks fine, but in fact the List contains null elements. After calling the IDeserializationCallback.OnDeserialization callback, the list is correct. I can't just call IEnumerable.ConvertAll <> () on it, because it looks like the serialization structure is trying to find the same instance when it returns to clear everything, and ConvertAll will create a new list.

Now it works for me, but I hope that someone there can help me clear it, because at the moment this is not all that can be saved. Here's what you need to do to do this:

 [Serializable] public class Zoo : ISerializable, IDeserializationCallback { List<IAnimal> m_List = null; List<Giraffe> m_LegacyList = null; //Just so that we can save an old-style zoo //Temp copy of the list List<Animal> m_List_Deserialization_Temp_Copy = null; public Zoo(bool legacy) { m_List = new List<IAnimal>(); if (legacy) { //Create an old style zoo, just for the example m_LegacyList = new List<Giraffe>(); m_LegacyList.Add(new Giraffe(0)); m_LegacyList.Add(new Giraffe(1)); } else { m_List.Add(new Animal(0)); m_List.Add(new Animal(1)); } } public List<IAnimal> List { get { return m_List; } } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { if(m_LegacyList != null) //Save as an old style list if we have old data info.AddValue("list", m_LegacyList); else info.AddValue("list", m_List); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] private Zoo(SerializationInfo info, StreamingContext context) { try { //New style m_List = (List<IAnimal>)info.GetValue("list", typeof(List<IAnimal>)); } catch (InvalidCastException) { //Old style //Put it in a temp list, until the OnDeserialization callback is called, this will be a list, full of null items! m_List_Deserialization_Temp_Copy = (List<Animal>)info.GetValue("list", typeof(List<Animal>)); } } void IDeserializationCallback.OnDeserialization(object sender) { if (m_List_Deserialization_Temp_Copy != null) { m_List = new List<IAnimal>(); //This works because IEnumerable<Animal> is covariant to IEnumerable<IAnimal> m_List.AddRange(m_List_Deserialization_Temp_Copy); } } } 

Here is a basic test console application that shows serialization and deserialization for both cases:

  static void Main(string[] args) { { var item = new Zoo(false); var formatter = new BinaryFormatter(); var stream = new MemoryStream(); formatter.Serialize(stream, item); stream.Position = 0; formatter.Binder = new LegacyZooSerializationBinder(); var deserialized = (Zoo)formatter.Deserialize(stream); Debug.Assert(deserialized.List.Count == 2); Debug.Assert(deserialized.List[0] != null); Debug.Assert(deserialized.List[0].Age == 0); Debug.Assert(deserialized.List[1] != null); Debug.Assert(deserialized.List[1].Age == 1); Console.WriteLine("New-style Zoo serialization OK."); } { var item = new Zoo(true); var formatter = new BinaryFormatter(); var stream = new MemoryStream(); formatter.Serialize(stream, item); stream.Position = 0; formatter.Binder = new LegacyZooSerializationBinder(); var deserialized = (Zoo)formatter.Deserialize(stream); Debug.Assert(deserialized.List.Count == 2); Debug.Assert(deserialized.List[0] != null); Debug.Assert(deserialized.List[0].Age == 0); Debug.Assert(deserialized.List[1] != null); Debug.Assert(deserialized.List[1].Age == 1); Console.WriteLine("Old-style Zoo serialization OK."); } Console.ReadKey(); } 

Any suggestions would be greatly appreciated. I find it hard to find good resources for this type of thing. Thanks!

+4
source share
1 answer

Try to make a one-time conversion from old files to a new format, preferably during installation and specifically after backing them up. Thus, you should not support this strange one-time serialization forever, and your code base becomes much simpler.

+3
source

Source: https://habr.com/ru/post/1401234/


All Articles