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

3

下面的程序试图对一个层次结构中的泛型类型对象进行序列化和反序列化,但是会出现下面代码后面列出的错误。

该怎么做才能让它正常工作呢?

代码:

[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'。遇到名称为'DerivedAOfstring',命名空间为的'Element'。

确实,在上面的主方法中,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个回答

3
你需要做的是始终使用 DataContractSerializer 进行派生类型的序列化和反序列化使用相同的Type参数进行构造。你正在尝试使用 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>))进行序列化,则反序列化将起作用,并且通过xsi:type属性的存在正确构造DerivedA<string>实例。


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) 它明确地说:

type Type: System.Type 被序列化或反序列化的实例的类型。

KnownType在这里不起作用。您必须明确指定要反序列化的类型,该类型必须与您在序列化中使用的类型匹配。

你可以尝试使用首先使用 MyBase 元数据对 MyDerived 实例进行序列化来解决问题:
var xml = Serialize((MyBase)foo);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接