以下程序尝试从层次结构中序列化然后反序列化泛型类型的对象,但失败并且代码下方列出了错误。
如何让它发挥作用?
[DataContract]
[KnownType(nameof(GetKnownTypes))]
public abstract class Base<T>
{
private static Type[] GetKnownTypes()
{
return new[] {typeof(DerivedA<T>)};
}
}
[DataContract]
public class DerivedA<T> : Base<T>
{
[DataMember]
public T Foo { get; set; }
}
class Program
{
private static void Main()
{
// This works fine
var foo = new DerivedA<string> { Foo = "foo" };
var xml = Serialize(foo);
var foo2 = Deserialize<DerivedA<string>>(xml);
Console.WriteLine(foo2.Foo); // foo
// This throws the exception below
// (from serializer.ReadObject() in the Deserialize method)
var foo3 = Deserialize<Base<string>>(xml);
}
private static string Serialize<T>(T o)
{
string result = null;
var serializer = new DataContractSerializer(o.GetType());
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, o);
stream.Position = 0;
var reader = new StreamReader(stream);
result = reader.ReadToEnd();
}
return result;
}
private static T Deserialize<T>(string xml)
{
var result = default(T);
var serializer = new DataContractSerializer(typeof(T));
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
writer.Write(xml);
writer.Flush();
stream.Position = 0;
result = (T)serializer.ReadObject(stream);
}
return result;
}
}
Unhandled Exception: System.Runtime.Serialization.SerializationException: Error in line 1 position 139. Expecting element 'BaseOfstring' from namespace 'http://schemas.datacontract.org/2004/07/ConsoleApplication7'.. Encountered 'Element' with name 'DerivedAOfstring', namespace 'http://schemas.datacontract.org/2004/07/ConsoleApplication7'.
at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlDictionaryReader reader)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(Stream stream)
at ConsoleApplication7.Program.Deserialize[T](String xml) in c:\users\tly01\documents\visual studio 2015\Projects\ConsoleApplication7\ConsoleApplication7\Program.cs:line 65
该消息中最有趣的部分非常清楚出现了什么问题:
期望来自命名空间的元素'BaseOfstring'。遇到'Element',名称为'DerivedAOfstring',名称空间。
果然,上面主要方法中的xml
就是这个(添加了空格):
<DerivedAOfstring
xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication7"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Foo>foo</Foo>
</DerivedAOfstring>
我意识到我不得不让序列化程序知道我的类型层次结构,但我在上面的列表中尝试了很多KnownType
属性的不同变体,并且它们都没有成效。这项工作的正确方法是什么?
答案 0 :(得分:3)
您需要做的是始终使用DataContractSerializer
constructed using identical Type
arguments序列化和反序列化派生类型。您正尝试使用DerivedA<T>
进行序列化,并使用Base<T>
进行反序列化。这种不一致会导致您遇到的问题。
发生这种情况的原因如下。通常,有两种常规方法可以将多态类型序列化为XML:
可以更改元素名称以表示派生类型。使用XmlSerializer
时,[XmlElement(elementName, subType)]
属性支持此功能。
元素可以使用标准{http://www.w3.org/2001/XMLSchema}type
属性显式声明其类型,通常缩写为xsi:type
。 XmlSerializer
通过[XmlInclude(subType)]
属性支持此机制。 DataContractSerializer
通过KnownTypeAttribute
。
DataContractSerializer
使用第二种机制的事实是导致问题的原因。序列化派生类的实例时,将为根元素名称发出派生类合同名称。但是,当您将派生类的实例序列化为其基类的实例时,基类合同名称将与另外的xsi:type
一起使用。要查看此内容,请按以下方式重构代码:
private static string Serialize<T>(T o)
{
return Serialize(o, null);
}
private static string Serialize<T>(T o, DataContractSerializer serializer)
{
string result = null;
serializer = serializer ?? new DataContractSerializer(o.GetType());
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, o);
stream.Position = 0;
var reader = new StreamReader(stream);
result = reader.ReadToEnd();
}
return result;
}
现在,如果你这样做:
var xml = Serialize(foo);
Debug.WriteLine(xml);
你会看到
<DerivedAOfstring
xmlns="http://schemas.datacontract.org/2004/07/Question37284138" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Foo>foo</Foo>
</DerivedAOfstring>
但如果你这样做
var baseXml = Serialize(foo, new DataContractSerializer(typeof(Base<string>)));
Debug.WriteLine(baseXml);
你会看到:
<BaseOfstring i:type="DerivedAOfstring"
xmlns="http://schemas.datacontract.org/2004/07/Question37284138" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Foo>foo</Foo>
</BaseOfstring>
注意区别?这意味着前者不能使用DataContractSerializer(typeof(Base<string>))
反序列化。但是如果你序列化使用
DataContractSerializer(typeof(Base<string>))
然后反序列化将起作用 - 并且由于存在DerivedA<string>
属性,正确构建了xsi:type
的实例。
答案 1 :(得分:0)
这完全符合设计。
首先,为什么要将通用部件带到这里?它似乎与问题没有任何关系。
让我们这么简单:
你有一个基类:
[DataContract]
[KnownType(typeof(MyDerived))]
public abstract class MyBase
{
...
}
继任者:
[DataContract]
public class MyDerived : MyBase
{
...
}
然后序列化类MyDerived
的实例。然后你试着反序列化它,但你没有告诉DataContractSerializer
它应该期望看到的类型,从你的代码:
var serializer = new DataContractSerializer(typeof(T));
您的T
等于MyBase
。所以基本上你试图欺骗DataContractSerializer
,即你告诉它应该期望得到一个MyBase
的实例是不可能的,它会在xml中看到MyDerived
实例的元数据。请注意,DataContractSerializer
没有看到完整的程序集名称,因此xml节点类型MyDerived
不足以让DataContractSerializer
执行任何操作。在这种情况下,它无权猜测KnownType
中的类型。
看这里:DataContractSerializer Constructor (Type) 它清楚地说:
型 键入:System.Type
。序列化或反序列化的实例的类型。
KnownType
在这里不起作用。您必须明确指定要反序列化的类型,该类型必须与序列化中使用的类型相匹配。
如果您首先使用MyDerived
元数据序列化MyBase
实例,则可以尝试解决此问题:
var xml = Serialize((MyBase)foo);