【问题标题】:Convert any object to a byte[]将任何对象转换为字节[]
【发布时间】:2011-06-19 09:40:38
【问题描述】:

我正在编写一个原型 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 中将任何对象转换为字节数组?

【问题讨论】:

  • 这在一般情况下不可能以任何有意义的方式实现(例如,考虑FileStream 的实例,或封装类似句柄的任何对象)。
  • 您是否打算让所有客户端都运行 .NET?如果答案是否定的,您应该考虑其他形式的序列化(XML、JSON 等)

标签: c# .net object byte


【解决方案1】:
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>();

【讨论】:

    【解决方案2】:

    使用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 |
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 试过了,在 .NET 4.6.1 和 Windows 10 上似乎对我不起作用。
      • 如果对象是 Char[] 则不会。
      【解决方案4】:

      一个额外的实现,它使用 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[] 反序列化回 object 并且不确定它是否有效,但已经测试了转换为 byte[] 的速度,它完全满足了我的需求。

      【讨论】:

        【解决方案5】:

        我宁愿使用“序列化”这个表达方式而不是“转换成字节”。序列化一个对象意味着将它转换为一个字节数组(或 XML,或其他东西),可以在远程盒子上使用来重新构造对象。在 .NET 中,Serializable attribute 标记其对象可以序列化的类型。

        【讨论】:

          【解决方案6】:

          像这样简单的东西怎么样?

          return ((object[])value).Cast<byte>().ToArray(); 
          

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              【解决方案8】:

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

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

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

              【讨论】:

              • 为什么你认为StructureToPtr + Copy 很慢?怎么会比序列化慢?有没有更快的解决方案?
              • 如果您将它用于由几个简单类型组成的小型结构,是的(这是很常见的情况),由于编组和四元复制(从对象到堆,从堆到字节),它很慢,从字节到堆,从堆到对象)。当使用 IntPtr 而不是字节时,它可能会更快,但在这种情况下不会。并且这些类型编写自己的序列化程序更快,只需将值放入字节数组。我并不是说它比内置序列化慢,也不是说它“非常慢”。
              • 我喜欢这种方法,因为它是逐字节映射的。这是用 C++ 映射交换内存的好方法。为你 +1。
              • 注意潜在用户,虽然非常聪明,但此答案不适用于结构数组、不能作为非托管结构编组的对象或在其层次结构中具有 ComVisible(false) 父级的对象。
              • 反序列化你是如何获得“大小”的?在var bytes = new byte[size];
              【解决方案9】:

              查看这篇文章: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;
              }
              

              【讨论】:

              • 正如this answer 的评论中提到的,MemorySteam 应该被包裹在一个using 块中。
              • 除此之外还有什么需要注意的吗?我以这种方式实现了它,格式化一个包含 3 个 int32 公共成员的对象会产生一个 244 字节长的 ByteArray。我是否对 C# 语法一无所知,或者有什么我可能会错过的东西?
              • 抱歉,我无法解决您的问题。可以发一下代码吗?
              • @kombsh 我尝试简写: [Serializable] class GameConfiguration { public map_options_t enumMapIndex;公共 Int32 iPlayerAmount;私有 Int32 iGameID; } 字节[] 包; GameConfiguration objGameConfClient = new GameConfiguration(); baPacket = BinModler.ObjectToByteArray(objGameConfClient);现在 baPacket 包含大约 244 字节的内容。我只是预计 12 点。
              • @kombsh 您可以在您的示例中显式处理一次性对象。
              【解决方案10】:

              使用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();
                  }
              }
              

              请注意,objobj 中的任何属性/字段(以及它们的所有属性/字段等等)都需要使用 Serializable attribute 进行标记才能成功序列化。

              【讨论】:

              • 小心处理另一侧的“任何”对象,因为它可能不再有意义(例如,如果该对象是文件句柄或类似对象)跨度>
              • 是的,正常的警告适用,但提醒人们注意它们并不是一个坏主意。
              • 将 MemoryStream 的使用包装在 using 块中可能是个好主意,因为它会急切地释放使用的内部缓冲区。
              • 这个方法是 .NET 绑定的吗?我可以使用 StructLayoutAtrribute 序列化 C 结构并通过套接字发送到 C 代码并期望 C 代码理解该结构吗?我猜不是?
              • BinaryFormatter 不再推荐。 BinaryFormatter 类型很危险,不推荐用于数据处理。有关详细信息,请参阅 Microsoft 文档。 aka.ms/binaryformatter BinaryFormatter 类型很危险,不推荐用于数据处理。应用程序应该尽快停止使用 BinaryFormatter,即使他们认为他们正在处理的数据是值得信赖的。 BinaryFormatter 不安全,无法确保安全。
              【解决方案11】:

              您可以在框架中使用built-in serialization tools 并序列化为MemoryStream。这可能是最直接的选项,但可能会产生比您的方案严格需要的更大的 byte[]。

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

              【讨论】:

                【解决方案12】:

                您正在寻找的是序列化。 .Net 平台有多种可用的序列化形式

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-08-15
                  • 2011-09-19
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-12-08
                  相关资源
                  最近更新 更多