【问题标题】:Fixed Object to Byte Array固定对象到字节数组
【发布时间】:2016-09-03 08:46:18
【问题描述】:

我有一个看起来像这样的简单对象:

public class Foo
{
   public UInt32 One { get; set; }
   public UInt32 Two { get; set; }
   public UInt32 Three { get; set; }
   public UInt32 Four { get; set; }
}

我尝试了我在网上某处找到的这段代码:

public byte[] ObjectToByteArray(Object obj)
{
    MemoryStream fs = new MemoryStream();
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(fs, obj);
    byte[] rval = fs.ToArray();
    fs.Close();
    return rval;
}

但不知何故,返回的字节数组的大小为 248 字节。
我希望它是 4 个字节 x 4 个字段 = 16 个字节。

问题
将固定对象转换为字节数组的最简洁方法是什么?
在这种情况下,结果数组的大小应该是 16 字节吗?

【问题讨论】:

    标签: c# bytearray


    【解决方案1】:

    BinaryFormatter 保存了大量类型信息以便能够正确反序列化。如果您想要紧凑的序列化或通过一些严格的协议进行通信,您必须明确地这样做:

    public byte[] ToByteArray()
    {
        List<byte> result = new List<byte>();
    
        result.AddRange(BitConverter.GetBytes(One));
        result.AddRange(BitConverter.GetBytes(Two));
        result.AddRange(BitConverter.GetBytes(Three));
        result.AddRange(BitConverter.GetBytes(Four));
    
        return result.ToArray();
    }
    

    在这里,我将您的每个 UInt32 转换为字节数组并将其存储在结果数组中。

    编辑
    原来还有另一种方法是使用structMarshal 首先你创建struct 并用类似的属性标记它:

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct MyStruct
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
        public string StringField;
    
        public int IntField;
    }
    

    这里LayoutKind.Sequential 告诉 clr 将字段按照与声明相同的顺序保存在内存中。如果没有Pack = 1,结构可能会占用比所需更多的内存。就像 struct 有一个 short 字段和一个 byte 只需要 3 个字节,但默认情况下它的大小很可能是 4(处理器有用于操作单字节、2 字节和 4 字节的指令,clr 每个牺牲一个字节struct 实例将机器代码的指令量减少一半)。现在您可以使用Marshal 来复制字节:

    public static byte[] GetBytes<T>(T str)
    {
        int size = Marshal.SizeOf(str);
        var bytes = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);
    
        try 
        {
             Marshal.StructureToPtr(str, ptr, true);
             Marshal.Copy(ptr, bytes, 0, size);
             return bytes;
        }
        finally 
        {
             Marshal.FreeHGlobal(ptr);
        }
    }
    

    简单类型一切正常。对于像string 这样的复杂类型,您必须使用MarshalAs 属性,它的使用有点复杂(例如,我告诉 clr 将字符串编组为固定 50 字节大小的数组)。

    【讨论】:

      【解决方案2】:

      这是另一种方式...哪种方式最好可能是一个见仁见智的问题。我喜欢阿托莫斯克的回答。将该答案与强制转换运算符重载相结合,您就有了一个非常优雅的解决方案。

      class Foo
      {
          public UInt32 One { get; set; }
          public UInt32 Two { get; set; }
          public UInt32 Three { get; set; }
          public UInt32 Four { get; set; }
      
          static public implicit operator byte[](Foo f)
          {
              MemoryStream m = new MemoryStream(16);
              BinaryWriter w = new BinaryWriter(m);
      
              w.Write(f.One);
              w.Write(f.Two);
              w.Write(f.Three);
              w.Write(f.Four);
              w.Close();
              m.Close();
      
              return m.ToArray();
          }
      
          static public explicit operator Foo(byte[] b)
          {
              Foo f = new Foo();
              MemoryStream m = new MemoryStream(b);
              BinaryReader r = new BinaryReader(m);
      
              f.One = r.ReadUInt32();
              f.Two = r.ReadUInt32();
              f.Three = r.ReadUInt32();
              f.Four = r.ReadUInt32();
      
              r.Close();
              m.Close();
      
              return f;
          }
      }
      
      class Program
      {
          static void Main(string[] args)
          {
              Foo f = new Foo() { One = 1, Two = 2, Three = 3, Four = 4 };
              byte[] b = (byte[])f;
              Console.WriteLine(b.Length);
      
              f = (Foo)b;
              Console.WriteLine("{0} {1} {2} {3}", f.One, f.Two, f.Three, f.Four);
      
              Console.ReadKey(true);
          }
      }
      

      【讨论】:

        【解决方案3】:

        请记住,当您使用 BinaryFormatter 序列化对象时,您会包含类型信息等内容。这会导致您看到的开销。

        如果您想将一个对象序列化为(在这种情况下)仅 16 个字节,那么您需要使用 StreamWriter 之类的东西手动完成。

        如果大小不是问题,那么在这种情况下,Binaryformatter 并没有错,也不需要太多代码,但这并不是最节省内存的方式。

        编辑:以下是您使用StreamWriter 的方法

         System.IO.MemoryStream stream = new System.IO.MemoryStream();  
        
         StreamWriter writer = new StreamWriter(stream);
        
         writer.Write(myObject.One);  // here's where we actually write the data to the stream
         writer.Write(myObject.Two);
         writer.Write(myObject.Three);
         writer.Write(myObject.Four);    
        
         writer.Flush();   // make sure all the data in the stream writer ends up in the 
                           // underlying stream
        
         byte[] result = stream.ToArray();  // here's your resulting byte array
        
         stream.Dispose();   // don't forget to dispose of the stream!        
        

        【讨论】:

        • 需要16个字节是的。有什么建议我应该如何使用 StreamWriter??
        • 我已经编辑了我的答案。如果按照我建议的方式进行操作,生成的字节数组应该是 16 个字节。请注意,这只是示例代码。我没有测试过它,它也没有使用using 语句,您应该将其用于实现IDisposable 的对象,例如MemoryStream。您可能决定在您的 Foo 对象中使其成为一个函数,因此您可以调用 foo.Serialize() 来获取您的字节数组。
        【解决方案4】:

        这是一种手动完成的方法,可以保证 16 字节。

        using System;
        using System.IO;
        using System.Linq;
        
        public class Foo 
        {
            public UInt32 One { get; set; }
            public UInt32 Two { get; set; }
            public UInt32 Three { get; set; }
            public UInt32 Four { get; set; }
        
            public Foo() {}
        
            public Foo(byte[] array)
            {
                One = BitConverter.ToUInt32(array,0);    
                Two = BitConverter.ToUInt32(array,4);
                Three = BitConverter.ToUInt32(array,8);    
                Four = BitConverter.ToUInt32(array,12);    
            }
            public byte[] toByteArray()
            {
                byte[] retVal =  new byte[16];
                byte[] b = BitConverter.GetBytes(One);
                Array.Copy(b, 0, retVal, 0, 4);
                b = BitConverter.GetBytes(Two);
                Array.Copy(b, 0, retVal, 4, 4);
                 b = BitConverter.GetBytes(Three);
                Array.Copy(b, 0, retVal, 8, 4);
                 b = BitConverter.GetBytes(Four);
                Array.Copy(b, 0, retVal, 12, 4);
                return retVal;
            }
        }
        public class P{
            public static void Main(string[] args) {
                Foo foo = new Foo();
                foo.One = 1;
                foo.Two = 2;
                foo.Three = 3;
                foo.Four = 4;
        
                byte[] arr  = foo.toByteArray();
                Console.WriteLine(arr.Length);
        
        
                Foo bar = new Foo(arr);
                Console.WriteLine(string.Format("{0} {1} {2} {3}", bar.One, bar.Two, bar.Three, bar.Four));
        
            }
        }
        

        输出:

        16
        1 2 3 4
        

        【讨论】:

          猜你喜欢
          • 2015-11-11
          • 1970-01-01
          • 2019-05-09
          • 1970-01-01
          • 2010-12-07
          • 2016-05-14
          • 1970-01-01
          • 2020-04-25
          • 1970-01-01
          相关资源
          最近更新 更多