【问题标题】:DataContractSerialization of generic type hierarchies泛型类型层次结构的 DataContractSerialization
【发布时间】:2016-09-14 00:38:32
【问题描述】:

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

如何让它发挥作用?

代码:

[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”、命名空间的“元素”。

果然,上面main方法中的xml就是这个(加了空格):

<DerivedAOfstring
    xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication7"
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Foo>foo</Foo>
</DerivedAOfstring>

我意识到我必须以某种方式让序列化程序知道我的类型层次结构,但我已经尝试了上面列表中KnownType 属性的许多不同变体,但没有一个是富有成果的。完成这项工作的正确方法是什么?

【问题讨论】:

    标签: c# generics inheritance serialization datacontractserializer


    【解决方案1】:

    您需要做的是始终使用DataContractSerializerconstructed using identical Type arguments 序列化和反序列化您的派生类型。您正在尝试使用DerivedA&lt;T&gt; 进行序列化,使用Base&lt;T&gt; 进行反序列化。这种不一致会导致您看到的问题。

    发生这种情况的原因如下。一般来说,多态类型可以通过两种常规方式序列化为 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&lt;string&gt;)) 反序列化。但是如果你 序列化 使用 DataContractSerializer(typeof(Base&lt;string&gt;)) 然后反序列化将起作用 - 并正确构造 DerivedA&lt;string&gt; 的实例,这要归功于 xsi:type 属性的存在。

    【讨论】:

      【解决方案2】:

      这一切看起来完全是设计使然。

      首先,你为什么把通用部分带到这里?好像和问题没什么关系。

      让我们把这个简单:

      你有一个基类:

      [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 在这里不起作用。您必须明确指定要反序列化的类型必须与您在序列化中使用的类型相匹配。

      如果您首先使用MyBase 元数据序列化MyDerived 实例,则可以尝试解决该问题:

      var xml = Serialize((MyBase)foo);
      

      【讨论】:

        猜你喜欢
        • 2013-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-03
        • 1970-01-01
        相关资源
        最近更新 更多