我找到了自己问题的部分答案。在调试器中检查SerializationInfo 的MemberNames 和MemberValues 属性会显示其中存储的成员的类型。 Dictionary<X^, Y^> 包含在 SerializationInfo 中,作为名称为 KeyValuePairs 并键入 array<System::Collections::Generic::KeyValuePair<X^, Y^>> ^ 的项目。有了这些信息,SerializationInfo 的GetValue 方法可以用来检索键值对,然后可以将它们转换并添加到正在填充的对象中。 SerializationBinder 可用于让一个类的反序列化构造函数同时处理另一个类的反序列化,从而在重命名类后允许向后兼容。以下代码显示了所有这些内容。
using namespace System;
using namespace System::IO;
using namespace System::Collections::Generic;
using namespace System::Runtime::Serialization;
typedef KeyValuePair<int, int> Foo1kvp;
[Serializable]
public ref class Foo1: Dictionary<int, int>, ISerializable
{
public:
Foo1() { }
virtual void GetObjectData(SerializationInfo^ info, StreamingContext context) override
{
info->AddValue("VERSION", 1);
__super::GetObjectData(info, context);
}
Foo1(SerializationInfo^ info, StreamingContext context)
{
array<Foo1kvp>^ members = (array<Foo1kvp>^) info->GetValue("KeyValuePairs", array<Foo1kvp>::typeid);
for each (Foo1kvp kvp in members)
{
this->Add(kvp.Key, kvp.Value);
}
Console::WriteLine("Deserializing Foo1");
}
};
typedef KeyValuePair<String^, int> Foo2kvp;
[Serializable]
public ref class Foo2: Dictionary<String^, int>, ISerializable
{
public:
Foo2() { }
virtual void GetObjectData(SerializationInfo^ info, StreamingContext context) override
{
info->AddValue("VERSION", 2);
__super::GetObjectData(info, context);
}
Foo2(SerializationInfo^ info, StreamingContext context)
{
int version = info->GetInt32("VERSION");
if (version == 1)
{
array<Foo1kvp>^ members = (array<Foo1kvp>^) info->GetValue("KeyValuePairs", array<Foo1kvp>::typeid);
for each (Foo1kvp kvp in members)
{
this->Add(kvp.Key.ToString(), kvp.Value);
}
Console::WriteLine("Deserializing Foo2 from Foo1");
}
else
{
array<Foo2kvp>^ members = (array<Foo2kvp>^) info->GetValue("KeyValuePairs", array<Foo2kvp>::typeid);
for each (Foo2kvp kvp in members)
{
this->Add(kvp.Key, kvp.Value);
}
Console::WriteLine("Deserializing Foo2");
}
}
};
ref class MyBinder sealed: public SerializationBinder
{
public:
virtual Type^ BindToType(String^ assemblyName, String^ typeName) override
{
if (typeName == "Foo1")
typeName = "Foo2";
return Type::GetType(String::Format("{0}, {1}", typeName, assemblyName));
}
};
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World");
Foo1^ foo1 = gcnew Foo1;
foo1->Add(2, 7);
foo1->Add(3, 5);
IFormatter^ formatter1 = gcnew Formatters::Binary::BinaryFormatter(); // no translation to Foo2
IFormatter^ formatter2 = gcnew Formatters::Binary::BinaryFormatter();
formatter2->Binder = gcnew MyBinder; // translate Foo1 to Foo2
FileStream^ stream;
try
{
// serialize Foo1
stream = gcnew FileStream("fooserialized.dat", FileMode::Create, FileAccess::Write);
formatter1->Serialize(stream, foo1);
stream->Close();
// deserialize Foo1 to Foo1
stream = gcnew FileStream("fooserialized.dat", FileMode::Open, FileAccess::Read);
Foo1^ foo1b = dynamic_cast<Foo1^>(formatter1->Deserialize(stream));
stream->Close();
Console::WriteLine("deserialized Foo1 from Foo1");
for each (Foo1kvp kvp in foo1b)
{
Console::WriteLine("{0} -> {1}", kvp.Key, kvp.Value);
}
// deserialize Foo1 to Foo2
stream = gcnew FileStream("fooserialized.dat", FileMode::Open, FileAccess::Read);
Foo2^ foo2 = dynamic_cast<Foo2^>(formatter2->Deserialize(stream));
stream->Close();
Console::WriteLine("deserialized Foo2 from Foo1");
for each (Foo2kvp kvp in foo2)
{
Console::WriteLine("{0} -> {1}", kvp.Key, kvp.Value);
}
// serialize Foo2
Foo2^ foo2b = gcnew Foo2;
foo2b->Add("Two", 7);
foo2b->Add("Three", 5);
stream = gcnew FileStream("fooserialized.dat", FileMode::Create, FileAccess::Write);
formatter2->Serialize(stream, foo2b);
stream->Close();
// deserialize Foo2 to Foo2
stream = gcnew FileStream("fooserialized.dat", FileMode::Open, FileAccess::Read);
Foo2^ foo2c = dynamic_cast<Foo2^>(formatter2->Deserialize(stream));
stream->Close();
Console::WriteLine("deserialized Foo2 from Foo2");
for each (Foo2kvp kvp in foo2c)
{
Console::WriteLine("{0} -> {1}", kvp.Key, kvp.Value);
}
}
catch (Exception^ e)
{
Console::WriteLine(e);
if (stream)
stream->Close();
}
return 0;
}
运行此代码时,输出为:
Hello World
Deserializing Foo1
deserialized Foo1 from Foo1
2 -> 7
3 -> 5
Deserializing Foo2 from Foo1
deserialized Foo2 from Foo1
2 -> 7
3 -> 5
Deserializing Foo2
deserialized Foo2 from Foo2
Two -> 7
Three -> 5
遗憾的是,如果类继承自 List,则同样不起作用,因为 List<T> 未实现 ISerializable,因此 __super::GetObjectData 调用在派生自 List<T> 的类中不可用。下面的代码展示了我如何让它在一个小型应用程序中为 List 工作。
using namespace System;
using namespace System::IO;
using namespace System::Collections::Generic;
using namespace System::Runtime::Serialization;
[Serializable]
public ref class Foo1: List<int>
{ };
int
OurVersionNumber(SerializationInfo^ info)
{
// Serialized Foo1 has no VERSION property, but Foo2 does have it.
// Don't use info->GetInt32("VERSION") in a try-catch statement,
// because that is *very* slow when corresponding
// SerializationExceptions are triggered in the debugger.
SerializationInfoEnumerator^ it = info->GetEnumerator();
int version = 1;
while (it->MoveNext())
{
if (it->Name == "VERSION")
{
version = (Int32) it->Value;
break;
}
}
return version;
}
[Serializable]
public ref class Foo2: List<String^>, ISerializable
{
public:
Foo2() { }
// NOTE: no "override" on this one, because List<T> doesn't provide this method
virtual void GetObjectData(SerializationInfo^ info, StreamingContext context)
{
info->AddValue("VERSION", 2);
int size = this->Count;
List<String^>^ list = gcnew List<String^>(this);
info->AddValue("This", list);
}
Foo2(SerializationInfo^ info, StreamingContext context)
{
int version = OurVersionNumber(info);
if (version == 1)
{
int size = info->GetInt32("List`1+_size");
array<int>^ members = (array<int>^) info->GetValue("List`1+_items", array<int>::typeid);
for each (int value in members)
{
if (!size--)
break; // done; the remaining 'members' slots are empty
this->Add(value.ToString());
}
Console::WriteLine("Deserializing Foo2 from Foo1");
}
else
{
List<String^>^ list = (List<String^>^) info->GetValue("This", List<String^>::typeid);
int size = list->Count;
this->AddRange(list);
size = this->Count;
Console::WriteLine("Deserializing Foo2");
}
}
};
ref class MyBinder sealed: public SerializationBinder
{
public:
virtual Type^ BindToType(String^ assemblyName, String^ typeName) override
{
if (typeName == "Foo1")
typeName = "Foo2";
return Type::GetType(String::Format("{0}, {1}", typeName, assemblyName));
}
};
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World");
Foo1^ foo1 = gcnew Foo1;
foo1->Add(2);
foo1->Add(3);
IFormatter^ formatter1 = gcnew Formatters::Binary::BinaryFormatter(); // no translation to Foo2
IFormatter^ formatter2 = gcnew Formatters::Binary::BinaryFormatter();
formatter2->Binder = gcnew MyBinder; // translate Foo1 to Foo2
FileStream^ stream;
try
{
// serialize Foo1
stream = gcnew FileStream("fooserialized.dat", FileMode::Create, FileAccess::Write);
formatter1->Serialize(stream, foo1);
stream->Close();
// deserialize Foo1 to Foo1
stream = gcnew FileStream("fooserialized.dat", FileMode::Open, FileAccess::Read);
Foo1^ foo1b = (Foo1^) formatter1->Deserialize(stream);
stream->Close();
Console::WriteLine("deserialized Foo1 from Foo1");
for each (int value in foo1b)
{
Console::WriteLine(value);
}
// deserialize Foo1 to Foo2
stream = gcnew FileStream("fooserialized.dat", FileMode::Open, FileAccess::Read);
Foo2^ foo2 = (Foo2^) formatter2->Deserialize(stream);
stream->Close();
Console::WriteLine("deserialized Foo2 from Foo1");
for each (String^ value in foo2)
{
Console::WriteLine(value);
}
// serialize Foo2
Foo2^ foo2b = gcnew Foo2;
foo2b->Add("Two");
foo2b->Add("Three");
stream = gcnew FileStream("fooserialized.dat", FileMode::Create, FileAccess::Write);
formatter2->Serialize(stream, foo2b);
stream->Close();
// deserialize Foo2 to Foo2
stream = gcnew FileStream("fooserialized.dat", FileMode::Open, FileAccess::Read);
Foo2^ foo2c = (Foo2^) formatter2->Deserialize(stream);
int size = foo2c->Count;
stream->Close();
Console::WriteLine("deserialized Foo2 from Foo2");
for each (String^ value in foo2c)
{
Console::WriteLine(value);
}
}
catch (Exception^ e)
{
Console::WriteLine(e);
if (stream)
stream->Close();
}
return 0;
}
此应用程序生成以下输出:
Hello World
deserialized Foo1 from Foo1
2
3
Deserializing Foo2 from Foo1
deserialized Foo2 from Foo1
2
3
Deserializing Foo2
deserialized Foo2 from Foo2
Two
Three
但是,当在一个非常大的应用程序中使用类似的代码来反序列化旧的、深度嵌套的数据时,我不断遇到SerializationExceptions,附加信息仅限于“引用了 ID 为 number 的对象在修复中但不存在。”,并且报告的小 数字 对我来说毫无意义。检查SerializationBinder处理的类型名称显示
System.Collections.Generic.KeyValuePair`2
还有
System.Collections.Generic.List`1
所以反引号后面的数字不是固定的。这个数字是怎么确定的?如果我将其他类添加到组合中,我可以确定它不会突然改变给定类吗?