Спасибо, что заглянули!
Я работаю над новой версией продукта, развернутого в полевых условиях.Мне нужно сохранить возможность десериализации выходных файлов из более старого программного обеспечения.
Вот надуманный пример:
У меня есть база клиентов с сериализованными файлами, к которым им необходим доступ.Для целей этого вопроса у них есть файл "Zoo" со списком жирафов.
[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");
}
}
Теперь мы развертываем новую версию нашего программного обеспечения "Zoo", и мы собираемсядля поддержки других животных, кроме жирафов, мы должны были сделать это с самого начала, но из-за нехватки времени нам пришлось выпустить 1.0 с набором функций только для жирафов.
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");
}
}
Я использую пользовательскийserializationBinder для десериализации старых жирафов как животных
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;
}
}
Проблема в том, что я не хочу, чтобы мой класс Zoo использовал список в качестве хранилища, а не список.Я хочу сделать это по двум причинам, в том числе и для расширения в будущем, чтобы мне было легче создавать макеты для модульного тестирования.
Десериализация нового списка IAnimal не является проблемой.Проблема возникает, когда я хочу десериализовать элементы старого стиля.Binder возвращает правильный тип, вызывается правильный конструктор десериализации, все выглядит хорошо, но список на самом деле полон пустых элементов.После вызова обратного вызова IDeserializationCallback.OnDeserialization список верен.Я не могу просто вызвать IEnumerable.ConvertAll <> (), потому что похоже, что среда сериализации пытается найти точно такой же экземпляр, когда возвращается, чтобы очистить все, и ConvertAll создаст новый список.
У меня это работает на данный момент, но я надеюсь, что кто-то там может помочь мне разобраться с этим, так как это не так уж и легко поддерживать на данный момент.Вот что нужно для этого:
[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);
}
}
}
Вот базовое тестовое консольное приложение, которое показывает сериализацию и десериализацию для обоих случаев:
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();
}
Любые предложения будут приняты с благодарностью.Мне трудно найти хорошие ресурсы для такого рода вещей.Спасибо!