You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为SerializableDictionary实现IXmlSerialization时遇序列化异常求助

问题分析与解决方案

问题背景

在为SerializableDictionary<TKey, TValue>类实现IXmlSerializable接口时,将对象序列化到Telerik.Windows.Zip创建的压缩包条目流(Entry Stream)时,抛出System.NotSupportedException,错误信息为Can't flush final block twice。异常发生在第二个序列化器对同一个XmlWriter调用Serialize方法时。

异常详情:

System.NotSupportedException
  HResult=0x80131515
  Message=Can't flush final block twice
  Source=Telerik.Windows.Zip
  StackTrace:
   at Telerik.Windows.Zip.OperationStream.FlushFinalBlock()
   at System.Xml.XmlUtf8RawTextWriter.Flush()
   at System.Xml.XmlWellFormedWriter.Flush()
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o)
   at MP.SharedToolbox.XmlInOut.SerializableDictionary`2.WriteXml(XmlWriter writer) in C:\Users\britikov\Documents\Projects\SharedToolbox\XmlInOut\SerializableDictionary.cs:line 69
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterRegulationItem.Write19_LoadCard(String n, String ns, LoadCard o, Boolean isNullable, Boolean needType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterRegulationItem.Write20_RegulationItem(String n, String ns, RegulationItem o, Boolean isNullable, Boolean needType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterRegulationItem.Write21_Regulation(Object o)

无内部异常。

已定位问题:直接序列化到Zip Entry Stream时触发异常,目前通过先序列化到MemoryStream再复制到Entry Stream的方式临时解决,但不清楚两者差异导致问题的原因,寻求正确实现方式。

原因分析

Telerik的OperationStream(Zip条目流的底层实现)在调用FlushFinalBlock()后会标记流为已完成,不允许再次调用该方法。而XmlSerializer.Serialize()在完成序列化时会自动调用XmlWriter.Flush(),后者最终会触发OperationStream.FlushFinalBlock()。当SerializableDictionary.WriteXml()方法内部多次调用XmlSerializer.Serialize()时,第一次序列化会触发FlushFinalBlock(),第二次调用时流已经处于“最终块已刷新”的状态,因此抛出异常。

而MemoryStream没有这种“最终块只能刷新一次”的限制,它的Flush()是无操作(NOP)或者仅确保数据写入内存,不会锁定流状态,因此多次调用不会报错。

正确实现方式

方案1:避免在WriteXml中多次触发XmlWriter.Flush

修改SerializableDictionary<TKey, TValue>.WriteXml方法,禁止XmlSerializer.Serialize()自动刷新流。通过创建XmlWriter时配置XmlWriterSettings.CloseOutput = false,仅在所有序列化操作完成后统一调用一次Flush()。

示例代码:

public void WriteXml(XmlWriter writer)
{
    var settings = new XmlWriterSettings
    {
        CloseOutput = false,
        OmitXmlDeclaration = writer.Settings.OmitXmlDeclaration,
        Encoding = writer.Settings.Encoding
    };

    using (var subWriter = XmlWriter.Create(writer, settings))
    {
        foreach (var kvp in this)
        {
            var keySerializer = new XmlSerializer(typeof(TKey));
            var valueSerializer = new XmlSerializer(typeof(TValue));
            
            subWriter.WriteStartElement("Item");
            subWriter.WriteStartElement("Key");
            keySerializer.Serialize(subWriter, kvp.Key);
            subWriter.WriteEndElement();
            subWriter.WriteStartElement("Value");
            valueSerializer.Serialize(subWriter, kvp.Value);
            subWriter.WriteEndElement();
            subWriter.WriteEndElement();
        }
        subWriter.Flush();
    }
}

方案2:使用Telerik Zip流的正确写入模式

确保在获取Zip条目流时使用正确的打开模式,在所有写入操作完成后再关闭流,避免在写入过程中触发流的最终刷新。

示例代码:

using (var zipStream = new FileStream("output.zip", FileMode.Create))
using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create))
{
    var entry = zipArchive.CreateEntry("data.xml");
    using (var entryStream = entry.Open())
    {
        var serializer = new XmlSerializer(typeof(Regulation));
        serializer.Serialize(entryStream, regulationObject);
    }
}

方案3:优化MemoryStream中转(复杂场景推荐)

如果序列化逻辑涉及多层嵌套或多次调用,MemoryStream中转的方式简单可靠。可以通过ArrayPool<byte>复用内存块,减少GC压力。

示例代码:

public void WriteXml(XmlWriter writer)
{
    foreach (var kvp in this)
    {
        using (var ms = new MemoryStream())
        {
            var keySerializer = new XmlSerializer(typeof(TKey));
            keySerializer.Serialize(ms, kvp.Key);
            ms.Position = 0;
            writer.WriteNode(XmlReader.Create(ms), true);
            
            ms.SetLength(0);
            var valueSerializer = new XmlSerializer(typeof(TValue));
            valueSerializer.Serialize(ms, kvp.Value);
            ms.Position = 0;
            writer.WriteNode(XmlReader.Create(ms), true);
        }
    }
}

总结

问题核心是Telerik Zip流的FlushFinalBlock()只能调用一次,而多次XmlSerializer.Serialize()会重复触发该操作。方案1、2可直接解决流限制问题,方案3是兼容性更好的折中方案,适合复杂序列化场景。

内容的提问来源于stack exchange,提问作者Snail

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 01:57:48