从内存映射文件反序列化 XML 时,如何修复“SerializationException:根级别的数据无效。第 1 行,位置 1”

问题描述 投票:0回答:1

从内存映射文件反序列化 XML 时,我在调用

DataContractSerializer.ReadObject()
时遇到以下错误:

System.Runtime.Serialization.SerializationException:'反序列化 SampleClass 类型的对象时出错。根级别的数据无效。第 1 行,位置 1。'

我之前使用

DataContractSerializer
将 XML 写入文件,因此它应该是有效的。 我该如何解决这个问题?

在下面添加我的示例代码:

using System.IO.MemoryMappedFiles;
using System.Runtime.Serialization;
using System.Runtime.Versioning;

namespace TestMigration
{
    public class Program
    {
        [SupportedOSPlatform("windows")]
        public static void Main(string[] args)
        {
            using (var mmf = MemoryMappedFile.CreateNew("TempName", 10240, MemoryMappedFileAccess.ReadWrite))
            {
                WriteToMemoryMappedFile(mmf);

                var infoObj = ReadFromMemoryMappedFile(mmf);

                Console.WriteLine(infoObj?.Name);
            }

        }
        private static void WriteToMemoryMappedFile(MemoryMappedFile mmf)
        {
            var serializer = new DataContractSerializer(typeof(SampleClass));

            var infoObj = new SampleClass { Name = "TestApp" };

            using (var stream = mmf.CreateViewStream(0, 0, MemoryMappedFileAccess.Write))
            {
                serializer.WriteObject(stream, infoObj);
                stream.Flush();
            }
        }

        private static SampleClass? ReadFromMemoryMappedFile(MemoryMappedFile mmf)
        {
            var serializer = new DataContractSerializer(typeof(SampleClass));

            using (var stream = mmf.CreateViewStream(0, 0, MemoryMappedFileAccess.Read))
            {
                stream.Position = 0;
                return (SampleClass?)serializer.ReadObject(stream);
            }
        }
    }
}

[DataContract]
public class SampleClass
{
    [DataMember]
    public string? Name { get; set; }
}

我尝试添加解决方法,例如使用 XmlReader、XmlDictionaryReader 等,但没有成功。

c# xml .net-8.0 datacontractserializer memory-mapped-files
1个回答
0
投票

您的问题是,创建的内存映射文件具有固定大小[1]。 因此,在将 XML 写入视图流后,该文件将包含 XML 以及足够的未初始化字节,以将长度填充到固定长度 10240。您可以通过在

stream.Length
中打印
WriteToMemoryMappedFile()
的值来确认这一点:

using (var stream = mmf.CreateViewStream(0, 0, MemoryMappedFileAccess.Write))
{
    Debug.WriteLine(stream.Length); // Prints 10240

由于尾部填充,您的内存映射文件是一个“格式错误的 XML 文档”。 由 XmlReader 内部创建的

DataContractSerializer.ReadObject()
旨在通过读取根对象的末尾来验证这一点,以确保整个文件实际上格式良好。因为它不是,所以你会得到你看到的异常。
那么您有哪些解决方法可供选择?

首先

,您可以将内存映射文件视为 XML 片段序列,并仅读取第一个片段。 首先介绍一下下面的扩展方法: public static partial class DataContractSerializerExtensions { public static IEnumerable<T?> ReadObjectFragments<T>(Stream stream, DataContractSerializer? serializer = null, bool closeInput = true) { var settings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment, CloseInput = closeInput, }; serializer ??= new DataContractSerializer(typeof(T)); using (var outerReader = XmlReader.Create(stream, settings)) { while (outerReader.Read()) { // Skip whitespace if (outerReader.NodeType == XmlNodeType.Element) using (var innerReader = outerReader.ReadSubtree()) { yield return (T?)serializer.ReadObject(innerReader); } } } } }

然后修改
ReadFromMemoryMappedFile()

如下:

private static SampleClass? ReadFromMemoryMappedFile(MemoryMappedFile mmf)
{
    using (var stream = mmf.CreateViewStream(0, 0, MemoryMappedFileAccess.Read))
    {
        return DataContractSerializerExtensions.ReadObjectFragments<SampleClass>(stream).FirstOrDefault();
    }
}

您的 
SampleClass

现在将成功反序列化,因为对

FirstOrDefault()
的调用与
ConformanceLevel.Fragment
的使用相结合,可以防止底层
XmlReader
尝试读取初始片段之外的内容。
演示小提琴#1 

这里

其次

,由于您的内存映射文件无论如何都不是格式良好的 XML,因此您可以考虑采用“消息框架”方法,在 XML 内容之前写入实际数据大小。 为此,首先创建以下通用方法: public static partial class MemoryMappedFileExtensions { static readonly int LongSize = Marshal.SizeOf<long>(); public static void WriteAndFrame<T>(this MemoryMappedFile mmf, T value, long offset = 0, DataContractSerializer? serializer = null) { long actualSize; serializer ??= new DataContractSerializer(typeof(T)); // Write the data contract data at offset 8. using (var stream = mmf.CreateViewStream(checked(offset + LongSize), 0, MemoryMappedFileAccess.Write)) { var startPosition = stream.Position; serializer.WriteObject(stream, value); stream.Flush(); actualSize = stream.Position - startPosition; } // Write the message frame size at offset 0. using (var accessor = mmf.CreateViewAccessor(offset, LongSize)) { accessor.Write(0, actualSize); } } public static T? ReadFromFrame<T>(this MemoryMappedFile mmf, long offset = 0, DataContractSerializer? serializer = null) { long actualSize; serializer ??= new DataContractSerializer(typeof(T)); // Read the message frame size from offset zero. using (var accessor = mmf.CreateViewAccessor(offset, LongSize)) { accessor.Read(0, out actualSize); } // Read the XML starting at offset 8 with the specified message frame size. using (var stream = mmf.CreateViewStream(offset + LongSize, actualSize, MemoryMappedFileAccess.Read)) { return (T?)serializer.ReadObject(stream); } } }

现在你的读写方法可以重写如下:

private static void WriteToMemoryMappedFile(MemoryMappedFile mmf)
{
    var infoObj = new SampleClass { Name = "TestApp" };
    
    mmf.WriteAndFrame(infoObj);
}

private static SampleClass? ReadFromMemoryMappedFile(MemoryMappedFile mmf) =>
    mmf.ReadFromFrame<SampleClass>();

演示小提琴 #2

这里

最后,您可能会考虑使用内存映射文件来保存单个 XML 文档的方法是否理想,因为文件大小必须提前固定。

[1]
请参阅

如何动态扩展内存映射文件进行讨论。

© www.soinside.com 2019 - 2024. All rights reserved.