提问人:Louis Strous 提问时间:1/25/2012 最后编辑:LU RDLouis Strous 更新时间:1/26/2012 访问量:850
如何处理基于泛型集合的已更改类的反序列化向后兼容性?
How to handle deserialization backward compatibility for changed classes based on generic collections?
问:
如果旧版本的 c++/CLI 应用程序序列化了一个从 Dictionary 派生的类,涉及 类型的键,而新版本需要将键类型更改为 ,那么我怎样才能最好地使应用程序支持读取旧的序列化数据(仍然基于)以及新的序列化数据(基于)?Foo
X
Z
X
Z
如果旧情况是这样的:
ref class Foo: Generic::Dictionary<X^, Y^>, ISerializable
{
public:
Foo(SerializationInfo^ info, StreamingContext context)
{
info->AddValue("VERSION", 1);
__super::GetObjectData(info, context);
}
virtual void GetObjectData(SerializationInfo^ info, StreamingContext context)
: Generic::Dictionary<X^, Y^>(info, context)
{
int version = info->GetInt32("VERSION");
/* omitted code to check version, act appropriately */
}
}
那么在新的形势下,我想做这样的事情:
ref class Foo: Generic::Dictionary<Z^, Y^>, ISerializable
{
public:
Foo(SerializationInfo^ info, StreamingContext context)
{
info->AddValue("VERSION", 2);
__super::GetObjectData(info, context);
}
virtual void GetObjectData(SerializationInfo^ info, StreamingContext context)
{
int version = info->GetInt32("VERSION");
if (version == 1)
{
Generic::Dictionary<X^, Y^> old
= gcnew Generic::Dictionary<X^, Y^>(info, context);
/* code here to convert "old" to new format,
assign to members of "this" */
}
else
{
Generic::Dictionary<Z^, Y^)(info, context);
}
}
}
但这失败了,编译错误类型为:
error C2248: 'System::Collections::Generic::Dictionary<TKey,TValue>::Dictionary' : cannot access protected member declared in class 'System::Collections::Generic::Dictionary<TKey,TValue>' with [ TKey=X ^, TValue=Y ^ ]
.
在更简单的情况下,我可以用来提取和处理单个数据成员,但在当前情况下,字典的序列化留给了 .NET(通过调用),我不知道如何使用来提取旧的字典。info->GetValue
__super::GetObjectData
info->GetValue
一个相关的问题:如果我想重命名为并且能够支持读取旧的序列化数据(仍然基于)以及新的序列化数据(基于),那么我怎样才能最好地做到这一点?Foo
BetterFoo
Foo
BetterFoo
我已经研究过了,但不知道如何使用它们来解决我的问题。SerializationBinder
ISerializationSurrogate
答:
我已经找到了我自己问题的部分答案。检查调试器中 的 和 属性将显示存储在其中的成员的类型。A 包含在 中,作为具有名称和类型的项目。有了这些信息,就可以使用 ' 方法来检索键值对,然后可以转换它们并将其添加到要填充的对象中。A 可用于让一个类的反序列化构造函数处理另一个类的反序列化,从而允许在重命名类后向后兼容。下面的代码显示了所有这些内容。MemberNames
MemberValues
SerializationInfo
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
但是,当在一个非常大的应用程序中使用类似的代码来反序列化旧的、深度嵌套的数据时,我总是遇到 s,附加信息仅限于“具有 ID 号的对象在修复中被引用但不存在”,并且报告的小数字对我来说毫无意义。检查节目处理的类型名称SerializationException
SerializationBinder
System.Collections.Generic.KeyValuePair`2
以及
System.Collections.Generic.List`1
所以反引号后面的数字不是固定的。这个数字是如何确定的?如果我将其他类添加到组合中,我可以确定它不会突然更改给定类吗?
评论