固定对象到字节数组

时间:2014-06-20 04:03:30

标签: c# bytearray

我有一个简单的对象,如下所示:

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个字节吗?

4 个答案:

答案 0 :(得分:6)

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字节大小的数组)。

答案 1 :(得分:3)

这是另一种方式......哪种方式最好是可能的意见问题。我喜欢Atomosk的答案。将该答案与演员操作员重载相结合,您就拥有了非常优雅的解决方案。

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);
    }
}

答案 2 :(得分:2)

请记住,当您使用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!        

答案 3 :(得分:2)

这是一种手动完成的方法,可以保证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