将任何对象转换为字节[]


138

我正在编写一个原型TCP连接,但在均匀化要发送的数据时遇到了一些麻烦。

目前,我只发送字符串,但是将来我们希望能够发送任何对象。

此刻的代码非常简单,因为我认为所有内容都可以转换为字节数组:

void SendData(object headerObject, object bodyObject)
{
  byte[] header = (byte[])headerObject;  //strings at runtime, 
  byte[] body = (byte[])bodyObject;      //invalid cast exception

  // Unable to cast object of type 'System.String' to type 'System.Byte[]'.
  ...
}

这当然很容易解决

if( state.headerObject is System.String ){...}

问题是,如果我这样做,我需要在运行时检查无法转换为byte []的所有类型的对象。

因为我不知道在运行时不能将每个对象都转换为byte [],所以这不是一个选择。

如何在C#.NET 4.0中将任何对象完全转换为字节数组?


2
通常,这不可能以任何有意义的方式来实现(例如,考虑的实例FileStream或封装此类句柄的任何对象)。
杰森

2
您打算让所有客户端都运行.NET吗?如果答案是否定的,那么您应该考虑某种其他形式的序列化(XML,JSON等)
R. Martinho Fernandes

Answers:


195

使用BinaryFormatter

byte[] ObjectToByteArray(object obj)
{
    if(obj == null)
        return null;
    BinaryFormatter bf = new BinaryFormatter();
    using (MemoryStream ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

请注意,obj其中的所有属性/字段obj(及其所有属性/字段都将如此)都需要标记为该Serializable属性,以便以此成功进行序列化。


13
请谨慎对待另一侧的“任何”对象,因为它可能不再有意义(例如,如果该对象是文件的句柄或类似文件)
Rowland Shaw

1
是的,通常会有一些警告,但要提醒人们是个不错的主意。
Daniel DiPaolo 2011年

24
将MemoryStream的使用包装在一个using块中可能是个好主意,因为它会急切释放所使用的内部缓冲区。
R. Martinho Fernandes

1
.NET是否绑定了此方法?我可以使用StructLayoutAtrribute序列化C结构并通过套接字发送到C代码,并期望C代码能够理解该结构吗?我猜不会?

103

查看这篇文章:http : //www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

使用下面的代码

// Convert an object to a byte array
private byte[] ObjectToByteArray(Object obj)
{
    if(obj == null)
        return null;

    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();
    bf.Serialize(ms, obj);

    return ms.ToArray();
}

// Convert a byte array to an Object
private Object ByteArrayToObject(byte[] arrBytes)
{
    MemoryStream memStream = new MemoryStream();
    BinaryFormatter binForm = new BinaryFormatter();
    memStream.Write(arrBytes, 0, arrBytes.Length);
    memStream.Seek(0, SeekOrigin.Begin);
    Object obj = (Object) binForm.Deserialize(memStream);

    return obj;
}

10
如对此答案的评论中所述,MemorySteam应当将其包装在一个using块中。
rookie1024

在报名上我有什么需要尊重的吗?我以这种方式实现了该方法,并且格式化包含3个int32公共成员的对象会导致244字节长的ByteArray。我是否不了解C#语法,或者可能会错过任何使用的东西?
dhein 2014年

抱歉,我无法解决您的问题。您可以发布代码吗?
kombsh14年

@kombsh我尝试以简写形式:[可序列化]类GameConfiguration {public map_options_t enumMapIndex; 公共Int32 iPlayerAmount; 私人Int32 iGameID;} byte [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration(); baPacket = BinModler.ObjectToByteArray(objGameConfClient); 现在,baPacket包含大约244字节的内容。我仅仅指刚预计12
dhein

1
@kombsh您可以在示例中显式处理一次性对象。
鲁道夫·德沃拉西克

30

就像其他人之前说过的那样,您可以使用二进制序列化,但是它可能会产生一个额外的字节,或者被反序列化为数据不完全相同的对象。另一方面,使用反射非常复杂且非常缓慢。还有另一种解决方案可以将您的对象严格转换为字节,反之亦然-编组:

var size = Marshal.SizeOf(your_object);
// Both managed and unmanaged buffers required.
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
// Copy object byte-to-byte to unmanaged memory.
Marshal.StructureToPtr(your_object, ptr, false);
// Copy data from unmanaged memory to managed buffer.
Marshal.Copy(ptr, bytes, 0, size);
// Release unmanaged memory.
Marshal.FreeHGlobal(ptr);

并将字节转换为对象:

var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, 0, ptr, size);
var your_object = (YourType)Marshal.PtrToStructure(ptr, typeof(YourType));
Marshal.FreeHGlobal(ptr);

与您自己的序列化字段(逐字段复制)相比,将这种方法用于小型对象和结构的方法相对于您自己的序列化字段明显要慢一些,并且是不安全的(因为从/到非托管内存的双重复制),但这是将对象严格转换为byte []而不实现序列化的最简单方法并且没有[Serializable]属性。


1
为什么您认为StructureToPtr+ Copy慢?怎么会比序列化慢呢?有没有更快的解决方案?
安东·萨姆索诺夫

如果将它用于由几个简单类型组成的小型结构,是的(这是很常见的情况),则由于编组和四元复制(从对象到堆,从堆到字节,从字节到堆,从堆)而变慢反对)。使用IntPtr代替字节时可能会更快,但在这种情况下则不会。对于此类类型,编写自己的序列化器(将值简单地放入字节数组中)的速度更快。我不是说它比内置序列化要慢,也不是说“该死的非常慢”。
Aberro

1
我喜欢这种方法,因为它按字节映射。这是使用C ++映射交换内存的一种非常好的方法。为您+1。
郝阮

2
请注意潜在的用户,尽管非常聪明,但是此答案不适用于结构数组,无法封送为非托管结构的对象或在其层次结构中具有ComVisible(false)父对象的对象。
TernaryTopiary

1
要反序列化您如何获得“尺寸”?于var bytes = new byte[size];
里卡多


10
public static class SerializerDeserializerExtensions
{
    public static byte[] Serializer(this object _object)
    {   
        byte[] bytes;
        using (var _MemoryStream = new MemoryStream())
        {
            IFormatter _BinaryFormatter = new BinaryFormatter();
            _BinaryFormatter.Serialize(_MemoryStream, _object);
            bytes = _MemoryStream.ToArray();
        }
        return bytes;
    }

    public static T Deserializer<T>(this byte[] _byteArray)
    {   
        T ReturnValue;
        using (var _MemoryStream = new MemoryStream(_byteArray))
        {
            IFormatter _BinaryFormatter = new BinaryFormatter();
            ReturnValue = (T)_BinaryFormatter.Deserialize(_MemoryStream);    
        }
        return ReturnValue;
    }
}

您可以像下面的代码一样使用它。

DataTable _DataTable = new DataTable();
_DataTable.Columns.Add(new DataColumn("Col1"));
_DataTable.Columns.Add(new DataColumn("Col2"));
_DataTable.Columns.Add(new DataColumn("Col3"));

for (int i = 0; i < 10; i++) {
    DataRow _DataRow = _DataTable.NewRow();
    _DataRow["Col1"] = (i + 1) + "Column 1";
    _DataRow["Col2"] = (i + 1) + "Column 2";
    _DataRow["Col3"] = (i + 1) + "Column 3";
    _DataTable.Rows.Add(_DataRow);
}

byte[] ByteArrayTest =  _DataTable.Serializer();
DataTable dt = ByteArrayTest.Deserializer<DataTable>();

6

使用Encoding.UTF8.GetBytes比使用更快MemoryStream。在这里,我正在使用NewtonsoftJson将输入对象转换为JSON字符串,然后从JSON字符串获取字节。

byte[] SerializeObject(object value) =>Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value));

此版本的@Daniel DiPaolo版本的基准

Method                    |     Mean |     Error |    StdDev |   Median |  Gen 0 | Allocated |
--------------------------|----------|-----------|-----------|----------|--------|-----------| 
ObjectToByteArray         | 4.983 us | 0.1183 us | 0.2622 us | 4.887 us | 0.9460 |    3.9 KB |
ObjectToByteArrayWithJson | 1.548 us | 0.0309 us | 0.0690 us | 1.528 us | 0.3090 |   1.27 KB |

2

扩展类中的组合解决方案:

public static class Extensions {

    public static byte[] ToByteArray(this object obj) {
        var size = Marshal.SizeOf(data);
        var bytes = new byte[size];
        var ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(data, ptr, false);
        Marshal.Copy(ptr, bytes, 0, size);
        Marshal.FreeHGlobal(ptr);
        return bytes;
   }

    public static string Serialize(this object obj) {
        return JsonConvert.SerializeObject(obj);
   }

}

1

您可以使用框架中的内置序列化工具并将其序列化为MemoryStream。这可能是最简单的选择,但所产生的byte []可能比您的方案所严格需要的更大。

如果是这种情况,您可以利用反射来迭代要序列化的对象中的字段和/或属性,然后将其手动写入MemoryStream,如果需要序列化非平凡的类型,则递归调用序列化。此方法更复杂,将花费更多时间来实现,但可以让您对序列化流进行更多控制。



1

我宁愿使用表达式“序列化”,也不愿使用“强制转换为字节”。序列化对象意味着将其转换为字节数组(或XML或其他内容),该字节数组可在远程包装箱上用于重建对象。在.NET中,Serializable属性标记其对象可以序列化的类型。


1

将对象转换为字节数组的另一种方法:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));

尝试过这一点,它似乎不适用于我在.NET 4.6.1和Windows 10上
。– Contango

0

另一种实现是使用Newtonsoft.Json二进制JSON,并且不需要使用[Serializable]属性标记所有内容。唯一的缺点是必须将对象包装在匿名类中,因此通过二进制序列化获得的字节数组可以与此对象不同。

public static byte[] ConvertToBytes(object obj)
{
    using (var ms = new MemoryStream())
    {
        using (var writer = new BsonWriter(ms))
        {
            var serializer = new JsonSerializer();
            serializer.Serialize(writer, new { Value = obj });
            return ms.ToArray();
        }
    }
}

使用匿名类是因为BSON应该以类或数组开头。我还没有尝试过将byte []反序列化为对象,也不确定是否可以正常工作,但是已经测试了转换为byte []的速度,它完全可以满足我的需求。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.