2017-03-08 101 views
1

我有一個實現IXmlSerializable的類。這個類包含一些屬性。序列化和反序列化類的單個實例工作正常。但是在集合類的情況下,序列化可以正常工作,但反序列化會永遠運行。這是一段代碼片段。我正在使用.Net 4.6.2。反序列化實現IXmlSerializable的類型集合永遠運行

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     this.A = Convert.ToInt32(reader.GetAttribute("A")); 
     this.B = Convert.ToInt32(reader.GetAttribute("B")); 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", this.A.ToString()); 
     writer.WriteAttributeString("B", this.B.ToString()); 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     var instance = new MyClass { A = 1, B = 2 }; 
     Serialize(instance); 
     instance = Deserialize<MyClass>();//works fine 

     var list = new List<MyClass> { new MyClass { A = 10, B = 20 } }; 
     Serialize(list); 
     list = Deserialize<List<MyClass>>();//runs forever 
    } 

    private static void Serialize(object o) 
    { 
     XmlSerializer ser = new XmlSerializer(o.GetType()); 
     using (TextWriter writer = new StreamWriter("xml.xml", false, Encoding.UTF8)) 
     { 
      ser.Serialize(writer, o); 
     } 
    } 

    private static T Deserialize<T>() 
    { 
     XmlSerializer ser = new XmlSerializer(typeof(T)); 
     using (TextReader reader = new StreamReader("xml.xml")) 
     { 
      return (T)ser.Deserialize(reader); 
     } 
    } 
} 

回答

1

您的問題是,作爲documentation解釋,ReadXml()必須消耗的包裝元素以及它的內容:

ReadXml方法必須使用的時候寫的信息重組的對象方法WriteXml

當調用此方法時,閱讀器位於包裝類型信息的開始標籤上。也就是說,直接在指示序列化對象開始的開始標籤上。 當此方法返回時,它必須從頭到尾讀取整個元素,包括其所有內容。與WriteXml方法不同,框架不會自動處理包裝器元素。您的實施必須這樣做。未能遵守這些定位規則可能會導致代碼生成意外的運行時異常或損壞的數據。

MyClass.ReadXml()不這樣做,這會導致無限循環時MyClass對象不是序列作爲根元素。相反,你必須MyClass看起來是這樣的:

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     /* 
     * https://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.readxml.aspx 
     * 
     * When this method is called, the reader is positioned at the start of the element that wraps the information for your type. 
     * That is, just before the start tag that indicates the beginning of a serialized object. When this method returns, 
     * it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, 
     * the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these 
     * positioning rules may cause code to generate unexpected runtime exceptions or corrupt data. 
     */ 
     var isEmptyElement = reader.IsEmptyElement; 
     this.A = XmlConvert.ToInt32(reader.GetAttribute("A")); 
     this.B = XmlConvert.ToInt32(reader.GetAttribute("B")); 
     reader.ReadStartElement(); 
     if (!isEmptyElement) 
     { 
      reader.ReadEndElement(); 
     } 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", XmlConvert.ToString(this.A)); 
     writer.WriteAttributeString("B", XmlConvert.ToString(this.B)); 
    } 
} 

現在你<MyClass>元素很簡單,沒有嵌套或可選元素。對於更復雜的自定義序列化,有幾種策略可以用來保證您的ReadXml()方法完全按照它應該的方式讀取,不多也不少。

首先,您可以撥打XNode.ReadFrom()將當前元素加載到XElement中。這需要更多的內存比直接從XmlReader解析,但更易於使用:

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     var element = (XElement)XNode.ReadFrom(reader); 
     this.A = (int)element.Attribute("A"); 
     this.B = (int)element.Attribute("B"); 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", XmlConvert.ToString(this.A)); 
     writer.WriteAttributeString("B", XmlConvert.ToString(this.B)); 
    } 
} 

其次,你可以使用XmlReader.ReadSubtree()以確保所需的XML內容被消耗:

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    protected virtual void ReadXmlSubtree(XmlReader reader) 
    { 
     this.A = XmlConvert.ToInt32(reader.GetAttribute("A")); 
     this.B = XmlConvert.ToInt32(reader.GetAttribute("B")); 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     // Consume all child nodes of the current element using ReadSubtree() 
     using (var subReader = reader.ReadSubtree()) 
     { 
      subReader.MoveToContent(); 
      ReadXmlSubtree(subReader); 
     } 
     reader.Read(); // Consume the end element itself. 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", XmlConvert.ToString(this.A)); 
     writer.WriteAttributeString("B", XmlConvert.ToString(this.B)); 
    } 
} 

最後幾點注意事項:

  • 一定要同時處理<MyClass /><MyClass></MyClass>。這兩種形式在語義上是相同的,發送系統也可以選擇。

  • 推薦使用XmlConvert類中的方法將原語從XML轉換爲XML。這樣做可以正確處理國際化。

  • 請務必使用或不使用縮進進行測試。有時候ReadXml()方法會消耗一個額外的XML節點,但是當啓用縮進時該bug將被隱藏 - 因爲它是被吃掉的空白節點。

  • 欲瞭解更多信息,請參閱How to Implement IXmlSerializable Correctly