【问题标题】:how to serialize / deserialize List<class object> with protobuf?如何使用 protobuf 序列化/反序列化 List<class object>?
【发布时间】:2017-03-28 22:18:04
【问题描述】:

我想序列化List&lt;ArchiveData&gt;,但它几乎总是失败。 Protobuff 抛出以下异常:

线型无效;这通常意味着您已经覆盖了一个文件 不截断或设置长度;看 Using Protobuf-net, I suddenly got an exception about an unknown wire-type

我已经阅读了帖子,但仍然找不到解决方案。如何序列化和反序列化?

我的类和结构:

[Serializable, ProtoContract(Name = @"Archive"), ProtoInclude(1, typeof(List<ArchiveData>))]
public partial class Archive : IExtensible
{
    [ProtoMember(1, IsRequired = true, OverwriteList = true, Name = @"data", DataFormat = DataFormat.Default)]
    public List<ArchiveData> data { get; set; }

    public Archive()
    {
        data = new List<ArchiveForm.ArchiveData>();
    }

    private IExtension extensionObject;
    IExtension IExtensible.GetExtensionObject(bool createIfMissing)
    {
        return Extensible.GetExtensionObject(ref extensionObject, createIfMissing);
    }
}

public struct ArchiveData
{
    [ProtoMember(1, IsRequired = false, OverwriteList = true, Name = @"sourcefolder", DataFormat = DataFormat.Default)]
    [global::System.ComponentModel.DefaultValue("")]
    public string sourcefolder { get; set; }

    [ProtoMember(2, IsRequired = false, OverwriteList = true, Name = @"destinationfolder", DataFormat = DataFormat.Default)]
    [global::System.ComponentModel.DefaultValue("")]
    public string destinationfolder { get; set; }

    [ProtoMember(3, IsRequired = false, OverwriteList = true, Name = @"period", DataFormat = DataFormat.FixedSize)]
    [global::System.ComponentModel.DefaultValue((int)0)]
    public int period { get; set; }

    public ArchiveData(string sfolder = "", string dfolder = "", int priod = 0)
    {
        sourcefolder = sfolder;
        destinationfolder = dfolder;
        period = priod;
    }
}

我使用以下方法对其进行序列化:

public static void Refresh(ref Archive arc)
{
    if (File.Exists(probuffile))
    {
        using (var fs = File.OpenRead(probuffile))
        {
            arc = Serializer.Deserialize<Archive>(fs);
        }
    }
}

我用以下方法反序列化:

public static void Update(Archive arc)
{
    using (var fs = File.Create(probuffile))
    {
        Serializer.Serialize<Archive>(fs, arc);
        fs.SetLength(fs.Position);
    }
}

我使用它:

Archive archive = new Archive();
//Add some ArchiveData.
Refresh(ref archive);

------------------编辑---- --------------

已添加此部分以获取更多信息。当我像下面的代码一样使用 SerializeWithLengthPrefix / DeserializeWithLengthPrefix 函数时,每次我使用 Deserialize 函数的第一个类都可以正常工作。但它为我使用反序列化函数的第二个类返回 null。

[Serializable, ProtoContract(Name = @"OptionData")]
public class OptionData : IExtensible
{
    [ProtoMember(1, IsRequired = false, OverwriteList = true, Name = @"StartWithWindows", DataFormat = DataFormat.Default)]
    [DefaultValue(false)]
    public bool StartWithWindows { get; set; }

    [ProtoMember(2, IsRequired = false, OverwriteList = true, Name = @"AutoBackup", DataFormat = DataFormat.Default)]
    [DefaultValue(false)]
    public bool AutoBackup { get; set; }

    [ProtoMember(3, IsRequired = false, OverwriteList = true, Name = @"Speed", DataFormat = DataFormat.FixedSize)]
    [DefaultValue((int)0)]
    public int Speed { get; set; }

    private IExtension extensionObject;
    IExtension IExtensible.GetExtensionObject(bool createIfMissing)
    {
        return Extensible.GetExtensionObject(ref extensionObject, createIfMissing);
    }
}

public static void Update(OptionData op)
{
    using (var fs = File.Create(probuffile))
    {
        Serializer.SerializeWithLengthPrefix(fs, op, PrefixStyle.Base128, 3);
    }
}

public static void Update(Archive arc)
{
    using (var fs = File.Create(probuffile))
    {
        Serializer.SerializeWithLengthPrefix<Archive>(fs, arc, PrefixStyle.Base128, 2);
    }
}

public static void Refresh(ref OptionData op)
{
    if (File.Exists(probuffile))
    {
        using (var fs = File.OpenRead(probuffile))
        {
            op = Serializer.DeserializeWithLengthPrefix<OptionData>(fs, PrefixStyle.Base128, 3);
        }
    }
}

public static void Refresh(ref Archive arc)
{
    if (File.Exists(probuffile))
    {
        using (var fs = File.OpenRead(probuffile))
        {
            arc = Serializer.DeserializeWithLengthPrefix<Archive>(fs, PrefixStyle.Base128, 2);
        }
    }
}

【问题讨论】:

  • 我在问题中看不到任何List&lt;object&gt; - 我可以假设这里的真正问题实际上只是“(反)序列化一个对象,我得到了线型错误”?
  • 对不起。我的错。我忘记写课了。
  • 重新编辑:它们在同一个文件中吗?或不同的文件?如果您想从同一个文件中连续读取两个内容,则需要在单个File.OpenRead 会话中连续读取它们。否则,每次调用File.OpenRead 时,它都会回到文件的开头并读取错误的数据。你没有展示如何使用UpdateRefresh,所以我不能100% 确定。同样,最好的办法 - 就像在我的回答中一样 - 是发布一些 完全可运行 代码(使用 Main() 方法等)来演示实际发生的问题。
  • 我放弃尝试序列化/反序列化两个不同的类。我将所有内容合二为一,并且工作正常。如果文件大小增加时速度变慢,我会尝试将类分成两个不同的类。感谢您的回答:)

标签: c# serialization protocol-buffers protobuf-net


【解决方案1】:

我很乐意提供帮助,但我正在努力让它失败。您说“但它几乎总是失败”,但以下可运行的控制台 exer 工作正常并写入各种大小的大量数据:

using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
static class P
{
    static void Main()
    {
        var random = new Random(12345);
        // start with 100
        var archive = CreateData(random, 100);
        Update(archive);
        Refresh(ref archive);

        // make it bigger
        archive = CreateData(random, 200);
        Update(archive);
        Refresh(ref archive);

        // make it smaller
        archive = CreateData(random, 50);
        Update(archive);
        Refresh(ref archive);

        // go wild
        for (int i = 0; i < 1000; i++)
        {
            archive = CreateData(random, random.Next(0, 500));
            Update(archive);
            Refresh(ref archive);
        }

    }
    static Archive CreateData(Random random, int dataCount)
    {
        var archive = new Archive();
        var data = archive.data;
        for (int i = 0; i < dataCount; i++)
        {
            data.Add(new ArchiveData
            {
                period = random.Next(10000),
                sourcefolder = CreateString(random, 50),
                destinationfolder = CreateString(random, 50)
            });
        }
        return archive;
    }

    private static unsafe string CreateString(Random random, int maxLength)
    {
        const string Alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
        int len = random.Next(maxLength + 1);
        char* chars = stackalloc char[len];
        for (int i = 0; i < len; i++)
        {
            chars[i] = Alphabet[random.Next(Alphabet.Length)];
        }
        return new string(chars, 0, len);
    }

    static string probuffile = "my.bin";
    public static void Refresh(ref Archive arc)
    {
        if (File.Exists(probuffile))
        {
            using (var fs = File.OpenRead(probuffile))
            {
                arc = Serializer.Deserialize<Archive>(fs);
                Console.WriteLine("Read: {0} items, {1} bytes", arc.data.Count, fs.Length);
            }
        }
    }

    public static void Update(Archive arc)
    {
        using (var fs = File.Create(probuffile))
        {
            Serializer.Serialize<Archive>(fs, arc);
            fs.SetLength(fs.Position);
            Console.WriteLine("Wrote: {0} items, {1} bytes", arc.data.Count, fs.Length);
        }
    }
}

[Serializable, ProtoContract(Name = @"Archive"), ProtoInclude(1, typeof(List<ArchiveData>))]
public partial class Archive : IExtensible
{
    [ProtoMember(1, IsRequired = true, OverwriteList = true, Name = @"data", DataFormat = DataFormat.Default)]
    public List<ArchiveData> data { get; set; }

    public Archive()
    {
        data = new List<ArchiveData>();
    }

    private IExtension extensionObject;
    IExtension IExtensible.GetExtensionObject(bool createIfMissing)
    {
        return Extensible.GetExtensionObject(ref extensionObject, createIfMissing);
    }
}
[ProtoContract]
public struct ArchiveData
{
    [ProtoMember(1, IsRequired = false, OverwriteList = true, Name = @"sourcefolder", DataFormat = DataFormat.Default)]
    [global::System.ComponentModel.DefaultValue("")]
    public string sourcefolder { get; set; }

    [ProtoMember(2, IsRequired = false, OverwriteList = true, Name = @"destinationfolder", DataFormat = DataFormat.Default)]
    [global::System.ComponentModel.DefaultValue("")]
    public string destinationfolder { get; set; }

    [ProtoMember(3, IsRequired = false, OverwriteList = true, Name = @"period", DataFormat = DataFormat.FixedSize)]
    [global::System.ComponentModel.DefaultValue((int)0)]
    public int period { get; set; }

    public ArchiveData(string sfolder = "", string dfolder = "", int priod = 0)
    {
        sourcefolder = sfolder;
        destinationfolder = dfolder;
        period = priod;
    }
}

我猜你的真实代码和上面的例子有什么不同:也是问题的原因。如果您帮助找出不同之处,我将非常乐意尽我所能提供帮助。

作为一个小提示:使用Create 时实际上不需要设置长度,因为这明确地“丢弃任何现有数据”。您需要担心长度的主要时间是人们使用OpenWrite 覆盖现有文件,然后写入比现有文件更少 的数据。但是在Create之后明确设置长度并没有问题

【讨论】:

  • 我终于意识到什么时候失败了。(我有另一个名为 OptionData 的可序列化类)。 1-)当我仅序列化/反序列化 OptionData 时,它可以正常工作。 2-)当我仅对存档进行序列化/反序列化时,它可以正常工作。但是 3-) 当我在 OptionData 和 Archive 都被序列化时反序列化 Archive 时,Archive 失败并且 OptionData 正常工作。
  • @BurakKocaman 如果你想让我调查一下,我需要一些代码。作为一个猜测:你是否在同一个流中连续序列化它们(即两次调用Serialize)?如果是这样,您将需要使用 *WithLengthPrefix 方法。但是,更简单的解决方法是创建一个根对象,其中两个项目作为成员 1/2,即[ProtoContract] class MyRoot { [ProtoMember(1)] public Archive Archive {get;set;} [ProtoMember(2)] public OptionData Options {get;set;} }
  • 感谢您的回答。我尝试使用 WithLengthPrefix,我认为当它不起作用时我应该寻找不同的解决方案。但是在你回答之后,我确定我用错了。我会尝试使用 WithLengthPrefix 直到它正常工作:D 再次感谢。
  • @BurakKocaman 如果你想让我修复你所拥有的,我可以做到 - 但我需要查看失败的代码
猜你喜欢
  • 1970-01-01
  • 2013-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
相关资源
最近更新 更多