【问题标题】:Good way to get size of class for binary serialization获取二进制序列化类大小的好方法
【发布时间】:2009-12-11 04:38:40
【问题描述】:

我正在编写一些将用于与 C# TCP 服务器通信的类,并且我正在使用 BinaryWriterBinaryReader 编写序列化/反序列化方法。这很容易,但我遇到了这样一种情况,我有一个类 Class1,其中包含一个实例 Class2。这两个类都实现了定义方法byte[] ToBytes()void FromBytes(byte[] data) 的接口。所以我有这个代码:

public class Class1
{
    public int Action;
    public Class2 Data;

    public void FromBytes(byte[] data)
    {
        BinaryReader br = new BinaryReader(new MemoryStream(data));
        Action= br.ReadInt32();
        Data = new Class2();
        Data.FromBytes(br.ReadBytes(___));
        br.Close();
    }
}

问题是,ReadBytes 调用的内容是什么?除了硬编码实现此接口的任何对象的大小之外,我不确定其他方法,但有没有更优雅的方法?我考虑过在运行时使用反射来计算它(缓存它以供将来调用),但这似乎更难看。

编辑:我应该指出,如果数据是可变长度的,那么二进制表示会在必要时包含数据长度,并且反序列化器会处理它。

【问题讨论】:

  • 序列化通常不会产生固定长度的表示。使用受约束的流或任何其他接受接收端在完成读取之前不知道长度的方法可能会做得更好。

标签: c# serialization


【解决方案1】:

通常,我不会传递byte[],而是传递Stream(可能是一个受约束的流,以阻止它在我们预期的区域之外读取)或包装阅读器,并使用它 - 即让类阅读它需要什么。

另一个简单的选项是在序列化期间为数据添加长度前缀,以便您提前知道大小。

或者,省去很多麻烦并使用预滚动的二进制序列化框架。有几个......他们都必须解决这个问题。乐于提供建议(这是我的宠物区)。

我也担心你班级的关注点分离,但这是一个附带问题。

【讨论】:

  • 我对 protobuf-net 很熟悉,但这将是一个比我目前想要的更大的承诺(也许,希望是下线)。有没有您熟悉并推荐的纯 .net 解决方案?
  • “纯 .net”是指“在 MS BCL 中”吗?可能不适合您的情况。不过,protobuf-net 是纯 .net。请注意,我什至没有提到它!不过,我不得不咬紧我的舌头。
【解决方案2】:

也许将实例的大小放在实例数据的开头?不过,如果您使用BinaryReader,您应该能够将阅读器本身传递给Class2 对象中的方法,然后它可以使用它来反序列化二进制数据。

【讨论】:

    【解决方案3】:

    如果我正确理解了您的问题,并且没有深入探讨您解决此问题的方法有多正确的优点,Marshal.SizeOf 应该可以解决您的问题。

    我冒昧地填补了您代码中的空白:


    class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        public class Class2
        {
            public int Data;
            public int SomeMoreData;
    
            internal void FromBytes(byte[] p)
            {
                BinaryReader br = new BinaryReader(new MemoryStream(p));
                Data = br.ReadInt32();
                SomeMoreData = br.ReadInt32();
                br.Close();
            }
        }
    
        public class Class1
        {
            public int Action;
            public Class2 Data;
    
            public void FromBytes(byte[] data)
            {
                BinaryReader br = new BinaryReader(new MemoryStream(data));
                Action = br.ReadInt32();
                Data = new Class2();
                int sizeOfClass2Instance = Marshal.SizeOf(Data);
                Data.FromBytes(br.ReadBytes(sizeOfClass2Instance));
                br.Close();
            }
        }
    
        static void Main(string[] args)
        {
            Class1 c1 = new Class1();
    
            int[] data = new int[3] { 1, 2, 3 };
            byte[] dataInBytes = new byte[data.Length * sizeof(int)];
    
            for (int i=0;i<data.Length;i++)
            {
                byte[] src = BitConverter.GetBytes(data[i]);
                Array.Copy(src, 0, dataInBytes, i * sizeof(int), sizeof(int));
            }
            c1.FromBytes(dataInBytes);
        }
    }
    

    诀窍在于,Marshal.SizeOf 依赖于您的类的 StructLayout 属性。您必须明确说明数据在类中的存储方式。

    【讨论】:

      【解决方案4】:

      如果您确定 Class2 的每个对象在使用 ToBytes 时传递相同数量的字节,请创建 Class2 的虚拟对象并使用 ToBytes().Length。

      或者只是:

          Data = new Class2();
          Data.FromBytes(br.ReadBytes(Data.ToBytes().Length));
      

      (显然,您可以通过将 Data.ToBytes().Length 存储在某处并重用它来提高效率)。

      【讨论】:

      • 希望数据中永远不会有任何字符串(或其他长度不可预测的数据);-p
      • 实际上没有,都是数字,但是一个公平的警告。也许我应该像其他人一样使用协议缓冲区;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-12
      • 2018-06-29
      • 1970-01-01
      • 2014-01-04
      • 2021-12-14
      • 1970-01-01
      • 2016-10-23
      相关资源
      最近更新 更多