【问题标题】:Why does XmlSerializer throws an Exception and raise a ValidationEvent when a schema validation error occurs inside IXmlSerializable.ReadXml()当 IXmlSerializable.ReadXml() 内部发生架构验证错误时,为什么 XmlSerializer 会引发异常并引发 ValidationEvent
【发布时间】:2020-02-28 09:47:25
【问题描述】:

我已经编写了一些测试来读取 XML 文件并根据 XSD 模式对其进行验证。 我的数据对象混合使用基于属性和自定义 IXmlSerializable 实现,并且我使用 XmlSerializer 执行反序列化。

我的测试涉及将未知元素插入 XML,使其不符合架构。然后我测试验证事件是否触发。

如果未知元素放置在 XML 中,因此它是基于属性的数据类之一的子元素(即属性用 XmlAttribute 和 XmlElement 属性修饰),则验证会正确触发。

但是,如果未知元素放置在 XML 中,因此它是 IXmlSerializable 类之一的子元素,则会引发 System.InvalidOperationException,但仍会触发验证。

自定义集合的 ReadXmlElements 中的代码创建一个新的 XmlSerializer 来读取子项,它是引发 InvalidOperationException 的 Deserialize 调用。

如果我在这个调用周围放置一个 try .. catch 块,它就会陷入无限循环。唯一的解决方案似乎是在顶级 XmlSerializer.Deserialize 调用周围放置一个 try-catch 块(如测试所示)。

有人知道为什么 XmlSerializer 会这样吗? 理想情况下,我想尝试在抛出异常的地方捕获异常,而不是使用顶级异常处理程序,所以还有一个次要问题是,如果 try..catch 块是,为什么代码会陷入无限循环添加到集合类中。

这里是抛出的异常:

System.InvalidOperationException: There is an error in XML document (13, 10). ---> System.InvalidOperationException: There is an error in XML document (13, 10). ---> System.InvalidOperationException: <UnknownElement xmlns='example'> was not expected.
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderGroup.Read1_Group()
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
   at XmlSerializerTest.EntityCollection~1.ReadXmlElements(XmlReader reader) in C:\source\repos\XmlSerializerTest\XmlSerializerTest\EntityCollection.cs:line 55
   at XmlSerializerTest.EntityCollection~1.ReadXml(XmlReader reader) in C:\Users\NGGMN9O\source\repos\XmlSerializerTest\XmlSerializerTest\EntityCollection.cs:line 41
   at System.Xml.Serialization.XmlSerializationReader.ReadSerializable(IXmlSerializable serializable, Boolean wrappedAny)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderExample.Read2_Example(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderExample.Read3_Example()
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
   at XmlSerializerTest.StackOverflowExample.InvalidElementInGroupTest() in C:\source\repos\XmlSerializerTest\XmlSerializerTest\XmlSerializerTest.cs:line 35

Schema.xsd

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:local="example"
           attributeFormDefault="unqualified"
           elementFormDefault="qualified"
           targetNamespace="example"
           version="1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <!--  Attribute Groups -->
  <xs:attributeGroup name="Identifiers">
    <xs:attribute name="Id"
                  type="xs:string"
                  use="required" />
    <xs:attribute name="Name"
                  type="xs:string"
                  use="required" />
  </xs:attributeGroup>
  <!-- Complex Types -->
  <xs:complexType abstract="true"
                  name="Entity">
    <xs:sequence>
      <xs:element name="Description"
                  type="xs:string"
                  minOccurs="0"
                  maxOccurs="1" />
    </xs:sequence>
    <xs:attributeGroup ref="local:Identifiers" />
  </xs:complexType>
  <xs:complexType name="DerivedEntity">
    <xs:complexContent>
      <xs:extension base="local:Entity">
        <xs:attribute name="Parameter"
                      use="required" />
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="Groups">
      <xs:sequence>
          <xs:element name="Group" type="local:Group" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
  </xs:complexType>
  <xs:complexType name="Group">
    <xs:complexContent>
      <xs:extension base="local:Entity">
        <xs:sequence>
          <xs:element name="DerivedEntity"
                      type="local:DerivedEntity"
                      minOccurs="0"
                      maxOccurs="unbounded" />
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <!-- Main Schema Definition -->
  <xs:element name="Example">
      <xs:complexType>
          <xs:sequence>
              <xs:element name="Groups"
                          type="local:Groups"
                          minOccurs="1"
                          maxOccurs="1" />
          </xs:sequence>
      </xs:complexType>
  </xs:element>
</xs:schema>

InvalidElementInGroup.xml

<?xml version="1.0"?>
<Example xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="example">
    <Groups>
        <Group Name="abc" Id="123">
            <DerivedEntity Id="123" Name="xyz" Parameter="ijk">
                <Description>def</Description>
            </DerivedEntity>
            <DerivedEntity Id="234" Name="bob" Parameter="12"/>
        </Group>
        <Group Name="def" Id="124">
            <Description>This is a description.</Description>
        </Group>
        <UnknownElement/>
    </Groups>
</Example>

实施 注意: 本示例中显示的代码不是生产代码。我知道我可以只使用支持序列化的List&lt;T&gt; 实现,而无需实现 IXmlSerializable。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace XmlSerializerTest
{
    public class Example
    {
        public Example()
        {
            Groups = new Groups();
        }

        public Groups Groups { get; set; }
    }

    public class Groups : EntityCollection<Group>
    {

    }
    public class Group : Entity, IXmlSerializable
    {
        private EntityCollection<DerivedEntity> entityCollection;

        public Group()
        {
            this.entityCollection = new EntityCollection<DerivedEntity>();
        }

        #region IXmlSerializable Implementation

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();

            // Read the attributes
            ReadXmlAttributes(reader);

            // Consume the start element
            bool isEmptyElement = reader.IsEmptyElement;
            reader.ReadStartElement();
            if (!isEmptyElement)
            {
                ReadXmlElements(reader);
                reader.ReadEndElement();
            }
        }

        /// <summary>
        /// Reads the XML elements.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public override void ReadXmlElements(XmlReader reader)
        {
            // Handle the optional base class description element
            base.ReadXmlElements(reader);

            entityCollection.ReadXmlElements(reader);
        }

        public void WriteXml(XmlWriter writer)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    public class EntityCollection<T> : IXmlSerializable, IList<T> where T : Entity
    {
        private List<T> childEntityField;

        public EntityCollection()
        {
            childEntityField = new List<T>();
        }

        #region IXmlSerializable Implementation

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();

            // Read the attributes
            ReadXmlAttributes(reader);

            // Consume the start element
            bool isEmptyElement = reader.IsEmptyElement;
            reader.ReadStartElement();
            if (!isEmptyElement)
            {
                ReadXmlElements(reader);
                reader.ReadEndElement();
            }
        }

        public virtual void ReadXmlAttributes(XmlReader reader)
        {
        }

        public virtual void ReadXmlElements(XmlReader reader)
        {
            XmlSerializer deserializer = new XmlSerializer(typeof(T), "example");
            while (reader.IsStartElement())
            {
                T item = (T)deserializer.Deserialize(reader);  // throws an InvalidOperationException if an unknown element is encountered.
                if (item != null)
                {
                    Add(item);
                }
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            throw new NotImplementedException();
        }
        #endregion

        #region IList Implementation

        public IEnumerator<T> GetEnumerator()
        {
            return childEntityField.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)childEntityField).GetEnumerator();
        }

        public void Add(T item)
        {
            childEntityField.Add(item);
        }

        public void Clear()
        {
            childEntityField.Clear();
        }

        public bool Contains(T item)
        {
            return childEntityField.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            childEntityField.CopyTo(array, arrayIndex);
        }

        public bool Remove(T item)
        {
            return childEntityField.Remove(item);
        }

        public int Count => childEntityField.Count;

        public bool IsReadOnly => ((ICollection<T>)childEntityField).IsReadOnly;

        public int IndexOf(T item)
        {
            return childEntityField.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            childEntityField.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            childEntityField.RemoveAt(index);
        }

        public T this[int index]
        {
            get => childEntityField[index];
            set => childEntityField[index] = value;
        }

        #endregion
    }

    [System.Xml.Serialization.XmlIncludeAttribute(typeof(DerivedEntity))]
    public abstract class Entity
    {

        public string Description { get; set; }

        public string Id { get; set; }

        public string Name { get; set; }

        public virtual void ReadXmlAttributes(XmlReader reader)
        {
            Id = reader.GetAttribute("Id");
            Name = reader.GetAttribute("Name");
        }

        public virtual void ReadXmlElements(XmlReader reader)
        {
            if (reader.IsStartElement("Description"))
            {
                Description = reader.ReadElementContentAsString();
            }
        }
    }

    public class DerivedEntity : Entity
    {
        public string Parameter { get; set; }
    }
}

测试

namespace XmlSerializerTest
{
    using System;
    using System.IO;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class StackOverflowExample
    {
        [TestMethod]
        [DeploymentItem(@"Schema.xsd")]
        [DeploymentItem(@"InvalidElementInGroup.xml")]
        public void InvalidElementInGroupTest()
        {
            // Open the file
            FileStream stream = new FileStream("InvalidElementInGroup.xml", FileMode.Open);

            // Configure settings
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.Schemas.Add(null, @"Schema.xsd");
            settings.ValidationType = ValidationType.Schema;
            settings.ValidationEventHandler += OnValidationEvent;

            XmlSerializer xmlDeserializer = new XmlSerializer(typeof(Example), "example");

            // Deserialize from the stream
            stream.Position = 0;
            XmlReader xmlReader = XmlReader.Create(stream, settings);

            try
            {
                Example deserializedObject = (Example)xmlDeserializer.Deserialize(xmlReader);
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e);
            }
        }

        private void OnValidationEvent(object sender, ValidationEventArgs e)
        {
            Console.WriteLine("Validation Event: " + e.Message);
        }
    }
}

【问题讨论】:

  • 能否请您 edit 在您的问题中包含 minimal reproducible example - 重现问题的最小模型和 XML?
  • 我现在更新了帖子以包含一个可重现的示例。这样做似乎我实际上回答了原始问题“为什么验证事件不会触发”,所以我还稍微修改了问题的措辞。
  • @TimCalladene 你想做什么?当您简单地序列化/反序列化列表时,为什么要提供自定义 IXmlSerializable 实现?你能edit 你的问题来解释验证失败并抛出InvalidOperationException 的问题吗?看起来一切正常,验证正在抱怨,Deserialize() 方法无法继续。

标签: c# xmlserializer ixmlserializable


【解决方案1】:

您的基本问题是您有一个抽象基类Entity,其继承者有时实现IXmlSerializable,有时不实现,当它们实现时,它们被包含在一个集合中,该集合也实现IXmlSerializable 并将集合属性与其 XML 中的集合子项混合在一起。在读取此 XML 的过程中,您没有正确推进 XmlReader,反序列化失败。

在实施IXmlSerializable 时,您需要遵守Marc Gravellthis answerProper way to implement IXmlSerializable? 中所述的规则以及文档:

对于IXmlSerializable.WriteXml(XmlWriter)

您提供的WriteXml 实现应该写出对象的XML 表示。框架编写一个包装器元素并在 XML 编写器启动后定位它。您的实现可能会编写其内容,包括子元素。然后框架关闭包装元素。

对于IXmlSerializable.ReadXml(XmlReader)

ReadXml 方法必须使用 WriteXml 方法写入的信息重构您的对象。

调用此方法时,阅读器将定位在包装您的类型信息的开始标记上。也就是说,直接在指示序列化对象开始的开始标记上。当此方法返回时,它必须从头到尾读取整个元素,包括其所有内容。与WriteXml 方法不同,框架不会自动处理包装元素。您的实施必须这样做。不遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏数据。

请特别注意ReadXml() 必须完全使用容器元素。这在继承场景中被证明是有问题的;是基类负责消费外部元素还是派生类?此外,如果某些派生类在读取过程中不正确地定位XmlReader,这可能会被单元测试忽略,但会导致 XML 文件中的后续数据在生产中被忽略或损坏。

因此创建一个用于读写IXmlSerializable对象的扩展框架是有意义的,其基类和派生类都具有自定义(反)序列化逻辑,其中容器元素、每个属性和每个子元素的处理是分开的:

public static class XmlSerializationExtensions
{
    public static void ReadIXmlSerializable(XmlReader reader, Func<XmlReader, bool> handleXmlAttribute, Func<XmlReader, bool> handleXmlElement, Func<XmlReader, bool> handleXmlText)
    {
        //https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.ixmlserializable.readxml?view=netframework-4.8#remarks
        //When this method is called, the reader is positioned on the start tag that wraps the information for your type. 
        //That is, directly on 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.
        reader.MoveToContent();
        if (reader.NodeType != XmlNodeType.Element)
            throw new XmlException(string.Format("Invalid NodeType {0}", reader.NodeType));
        if (reader.HasAttributes)
        {
            for (int i = 0; i < reader.AttributeCount; i++)
            {
                reader.MoveToAttribute(i);
                handleXmlAttribute(reader);
            }
            reader.MoveToElement(); // Moves the reader back to the element node.
        }
        if (reader.IsEmptyElement)
        {
            reader.Read();
            return;
        }
        reader.ReadStartElement(); // Advance to the first sub element of the wrapper element.
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            if (reader.NodeType == XmlNodeType.Element)
            {
                using (var subReader = reader.ReadSubtree())
                {
                    subReader.MoveToContent();
                    handleXmlElement(subReader);
                }
                // ReadSubtree() leaves the reader positioned ON the end of the element, so read that also.
                reader.Read();
            }
            else if (reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA)
            {
                var type = reader.NodeType;
                handleXmlText(reader);
                // Ensure that the reader was not advanced.
                if (reader.NodeType != type)
                    throw new XmlException(string.Format("handleXmlText incorrectly advanced the reader to a new node {0}", reader.NodeType));
                reader.Read();
            }
            else // Whitespace, comment
            {
                // Skip() leaves the reader positioned AFTER the end of the node.
                reader.Skip();
            }
        }
        // Move past the end of the wrapper element
        reader.ReadEndElement();
    }

    public static void WriteIXmlSerializable(XmlWriter writer, Action<XmlWriter> writeAttributes, Action<XmlWriter> writeNodes)
    {
        //https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.ixmlserializable.writexml?view=netframework-4.8#remarks
        //The WriteXml implementation you provide should write out the XML representation of the object. 
        //The framework writes a wrapper element and positions the XML writer after its start. Your implementation may write its contents, including child elements. 
        //The framework then closes the wrapper element.
        writeAttributes(writer);
        writeNodes(writer);
    }
}

然后,如下修改你的数据模型:

public class Constants
{
    public const string ExampleNamespace = "example";
}

[XmlRoot(Namespace = Constants.ExampleNamespace)]
public class Example
{
    public Example()
    {
        Groups = new Groups();
    }

    public Groups Groups { get; set; }
}

public class Groups : EntityCollection<Group>
{

}

public class EntityCollection<T> : IXmlSerializable, IList<T> where T : Entity
{
    private List<T> childEntityField;

    public EntityCollection()
    {
        childEntityField = new List<T>();
    }

    #region IXmlSerializable Implementation

    public XmlSchema GetSchema() { return null; }

    protected internal virtual bool HandleXmlAttribute(XmlReader reader) { return false; }

    protected internal virtual void WriteAttributes(XmlWriter writer) { }

    protected internal virtual bool HandleXmlElement(XmlReader reader)
    {
        var serializer = new XmlSerializer(typeof(T), Constants.ExampleNamespace);
        if (serializer.CanDeserialize(reader))
        {
            T item = (T)serializer.Deserialize(reader);
            if (item != null)
                Add(item);
            return true;
        }
        return false;
    }

    protected internal virtual void WriteNodes(XmlWriter writer)
    {
        var serializer = new XmlSerializer(typeof(T), Constants.ExampleNamespace);
        foreach (var item in this)
        {
            serializer.Serialize(writer, item);
        }
    }

    public void ReadXml(XmlReader reader)
    {
        XmlSerializationExtensions.ReadIXmlSerializable(reader, r => HandleXmlAttribute(r), r => HandleXmlElement(r), r => false);
    }

    public void WriteXml(XmlWriter writer)
    {
        XmlSerializationExtensions.WriteIXmlSerializable(writer, w => WriteAttributes(w), w => WriteNodes(w));
    }

    #endregion

    #region IList Implementation

    public IEnumerator<T> GetEnumerator()
    {
        return childEntityField.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)childEntityField).GetEnumerator();
    }

    public void Add(T item)
    {
        childEntityField.Add(item);
    }

    public void Clear()
    {
        childEntityField.Clear();
    }

    public bool Contains(T item)
    {
        return childEntityField.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        childEntityField.CopyTo(array, arrayIndex);
    }

    public bool Remove(T item)
    {
        return childEntityField.Remove(item);
    }

    public int Count { get { return childEntityField.Count; } }

    public bool IsReadOnly { get { return ((ICollection<T>)childEntityField).IsReadOnly; } }

    public int IndexOf(T item)
    {
        return childEntityField.IndexOf(item);
    }

    public void Insert(int index, T item)
    {
        childEntityField.Insert(index, item);
    }

    public void RemoveAt(int index)
    {
        childEntityField.RemoveAt(index);
    }

    public T this[int index]
    {
        get { return childEntityField[index]; }
        set { childEntityField[index] = value; }
    }

    #endregion
}

public class Group : Entity, IXmlSerializable
{
    private EntityCollection<DerivedEntity> entityCollection;

    public Group()
    {
        this.entityCollection = new EntityCollection<DerivedEntity>();
    }

    #region IXmlSerializable Implementation

    public XmlSchema GetSchema()
    {
        return null;
    }

    protected override bool HandleXmlElement(XmlReader reader)
    {
        if (base.HandleXmlElement(reader))
            return true;
        return entityCollection.HandleXmlElement(reader);
    }

    protected override void WriteNodes(XmlWriter writer)
    {
        base.WriteNodes(writer);
        entityCollection.WriteNodes(writer);
    }

    protected override bool HandleXmlAttribute(XmlReader reader)
    {
        if (base.HandleXmlAttribute(reader))
            return true;
        if (entityCollection.HandleXmlAttribute(reader))
            return true;
        return false;
    }

    protected override void WriteAttributes(XmlWriter writer)
    {
        base.WriteAttributes(writer);
        entityCollection.WriteAttributes(writer);
    }

    public void ReadXml(XmlReader reader)
    {
        XmlSerializationExtensions.ReadIXmlSerializable(reader, r => HandleXmlAttribute(r), r => HandleXmlElement(r), r => false);
    }

    public void WriteXml(XmlWriter writer)
    {
        XmlSerializationExtensions.WriteIXmlSerializable(writer, w => WriteAttributes(w), w => WriteNodes(w));
    }

    #endregion
}

public class DerivedEntity : Entity
{
    [XmlAttribute]
    public string Parameter { get; set; }
}

[System.Xml.Serialization.XmlIncludeAttribute(typeof(DerivedEntity))]
public abstract class Entity
{
    [XmlElement]
    public string Description { get; set; }

    [XmlAttribute]
    public string Id { get; set; }

    [XmlAttribute]
    public string Name { get; set; }

    protected virtual void WriteAttributes(XmlWriter writer)
    {
        if (Id != null)
            writer.WriteAttributeString("Id", Id);
        if (Name != null)
            writer.WriteAttributeString("Name", Name);
    }

    protected virtual bool HandleXmlAttribute(XmlReader reader)
    {
        if (reader.LocalName == "Id")
        {
            Id = reader.Value;
            return true;
        }
        else if (reader.LocalName == "Name")
        {
            Name = reader.Value;
            return true;
        }
        return false;
    }

    protected virtual void WriteNodes(XmlWriter writer)
    {
        if (Description != null)
        {
            writer.WriteElementString("Description", Description);
        }
    }

    protected virtual bool HandleXmlElement(XmlReader reader)
    {
        if (reader.LocalName == "Description")
        {
            Description = reader.ReadElementContentAsString();
            return true;
        }
        return false;
    }
}

您将能够成功反序列化和重新序列化Example。演示小提琴here.

注意事项:

  • 认真考虑简化此架构。这太复杂了。

  • 将在 &lt;Groups&gt; 内为 &lt;UnknownElement/&gt; 正确引发单个验证事件,因为架构中没有出现此类元素。

  • 当根 XML 元素名称和命名空间与预期的名称和命名空间不匹配时,XmlSerializer.Deserialize() 将抛出 InvalidOperationException。您可以通过调用XmlSerializer.CanDeserialize(XmlReader)来检查名称和命名空间是否正确。

  • 一定要测试带和不带缩进的 XML 反序列化。有时ReadXml() 方法会使阅读器前进一个节点,但如果 XML 包含无关紧要的缩进(即格式化),则不会造成任何损害,因为只会跳过无关紧要的空白节点。

  • 在派生类中重写Entity.HandleXmlElement(XmlReader reader) 时,应首先调用基类方法。如果基类方法处理元素,则返回true,派生类不应尝试处理它。同样,如果派生类处理该元素,则应将true 返回给更多派生类,指示该元素已被处理。 false 在类和基类都不能处理元素时返回。

  • XmlReader.ReadSubtree() 可用于确保某些派生类不会在HandleXmlElement(XmlReader reader) 中错误定位XmlReader

  • 如果您使用除new XmlSerializer(Type)new XmlSerializer(Type, String) 之外的任何构造函数来构造XmlSerializer,则必须只构造一次并静态缓存它以避免严重的内存泄漏。原因请参见the documentationMemory Leak using StreamReader and XmlSerializer。您没有在示例代码中以这种方式构建序列化程序,但可能在您的生产代码中这样做。

【讨论】:

  • 感谢您的详细解答。它非常有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多