DataContract泛型类型层次结构的序列化

时间:2016-05-17 18:52:51

标签: c# generics inheritance serialization datacontractserializer

以下程序尝试从层次结构中序列化然后反序列化泛型类型的对象,但失败并且代码下方列出了错误。

如何让它发挥作用?

代码:

[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属性的不同变体,并且它们都没有成效。这项工作的正确方法是什么?

2 个答案:

答案 0 :(得分:3)

您需要做的是始终使用DataContractSerializer constructed using identical Type arguments序列化和反序列化派生类型。您正尝试使用DerivedA<T>进行序列化,并使用Base<T>进行反序列化。这种不一致会导致您遇到的问题。

发生这种情况的原因如下。通常,有两种常规方法可以将多态类型序列化为XML:

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);