如何在C#中将对象转换为字节数组


99

我有一些需要写入二进制文件的对象集合。

我需要压缩文件中的字节,因此不能使用BinaryFormatterBinaryFormatter满足反序列化需求的各种信息。

如果我尝试

byte[] myBytes = (byte[]) myObject 

我遇到运行时异常。

我需要这个要快,所以我宁愿不要在周围复制字节数组。我只想演员byte[] myBytes = (byte[]) myObject们上班!

只是为了清楚起见,我不能在输出文件中包含任何元数据。只是对象字节。打包的对象到对象。根据收到的答案,我似乎将编写低级Buffer.BlockCopy代码。也许使用不安全的代码。

Answers:


173

要将对象转换为字节数组:

// Convert an object to a byte array
public static byte[] ObjectToByteArray(Object obj)
{
    BinaryFormatter bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

您只需要将此函数复制到代码中,然后将需要转换为字节数组的对象发送给它即可。如果需要再次将字节数组转换为对象,则可以使用以下函数:

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

您可以将这些功能与自定义类一起使用。您只需要[Serializable]在类中添加属性即可启用序列化


9
我试过了,它添加了各种元数据。OP表示他不想要元数据。
user316117 2013年

4
更不用说,每个人似乎都假设您要序列化的内容是您已经编写的,或者已经预先设置好要序列化的内容。
Hexum064

3
您可以MemoryStream在第二个代码示例中将字节数组直接传递给的构造函数。这将消除对Write(...)和的使用Seek(...)
unknown6656 '17

41

如果希望序列化的数据非常紧凑,则可以自己编写序列化方法。这样,您将有最少的开销。

例:

public class MyClass {

   public int Id { get; set; }
   public string Name { get; set; }

   public byte[] Serialize() {
      using (MemoryStream m = new MemoryStream()) {
         using (BinaryWriter writer = new BinaryWriter(m)) {
            writer.Write(Id);
            writer.Write(Name);
         }
         return m.ToArray();
      }
   }

   public static MyClass Desserialize(byte[] data) {
      MyClass result = new MyClass();
      using (MemoryStream m = new MemoryStream(data)) {
         using (BinaryReader reader = new BinaryReader(m)) {
            result.Id = reader.ReadInt32();
            result.Name = reader.ReadString();
         }
      }
      return result;
   }

}

我要写几个整数和几个字符串是什么?
史密斯

1
@Smith:是的,你可以做到,只需要一个个写就可以。在BinaryWriter将它们写在一个格式的BinaryReader可以阅读,只要你写,并以相同的顺序读取它们。
Guffa

1
BinaryWriter/Reader和使用BinaryFormatter
Smith

3
@Smith:使用BinaryWriter/Reader您自己进行序列化/反序列化,并且可以尽可能紧凑地仅写入/读取绝对需要的数据。在BinaryFormatter使用反射来找出数据的读/写,并使用的格式,对所有可能的情况下工作。它还在流中包含有关格式的元信息,因此增加了更多开销。
古法(Guffa)2015年

1
@Smith:可以将枚举强制转换为int(或如果您指定了任何其他类型作为枚举的存储空间)并编写它。阅读时,可以将其转换为枚举类型。
Guffa

31

那么从铸造myObjectbyte[]永远不会工作,除非你有一个明确的转换,或者myObject 一个byte[]。您需要某种类型的序列化框架。那里有很多东西,包括我附近和亲爱的协议缓冲区。就时间和空间而言,这都是“精简而卑鄙的”。

您会发现几乎所有的序列化框架都对可序列化内容有很大的限制,但是-由于跨平台,协议缓冲区比某些协议缓冲区更多。

如果您可以提出更多要求,我们可以为您提供更多帮助-但这绝不会像铸造那样简单...

编辑:只是为了对此作出响应:

我需要我的二进制文件来包含对象的字节。仅字节,无元数据。打包的对象到对象。因此,我将实现自定义序列化。

请记住,对象中的字节经常是引用...,因此您需要弄清楚如何处理它们。

我怀疑您会发现设计和实现自己的自定义序列化框架比您想象的要难。

我个人建议,如果您只需要为某些特定类型执行此操作,则不要费心尝试提出一个通用的序列化框架。只需在所需的所有类型中实现实例方法和静态方法:

public void WriteTo(Stream stream)
public static WhateverType ReadFrom(Stream stream)

需要牢记的一件事:如果您涉及继承,那么一切都会变得更加棘手。没有继承,如果您知道开始使用的类型,则无需包括任何类型信息。当然,还有版本控制的问题-您是否需要担心与类型的不同版本之间的向后和向前兼容性?


对我来说,将它称为“ protobuf-csharp-port”(Google代码)还是“ dotnet-protobufs”(Git)是否更正确?
马克·格雷夫

1
我需要我的二进制文件来包含对象的字节。仅字节,无元数据。打包的对象到对象。因此,我将实现自定义序列化。
chuckhlogan

6
元数据为零的风险是您那时版本非常无礼,因为在为时已晚之前,它几乎没有允许灵活性的方法。协议缓冲区是非常密集的数据。您是否真的需要额外旋转螺丝?
马克·格雷夫

@Marc:当然,对于整数,PB最终会比原始字节更密集...
Jon Skeet

16

我接受了Crystalonics的回答,并将其转变为扩展方法。我希望其他人会发现它们有用:

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

public static T Deserialize<T>(this byte[] byteArray) where T : class
{
    if (byteArray == null)
    {
        return null;
    }
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(byteArray, 0, byteArray.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = (T)binForm.Deserialize(memStream);
        return obj;
    }
}

1
这个真的非常有用和容易!!谢谢。
MrHIDEn '16

13

您实际上是在谈论序列化,它可以采取多种形式。由于您需要较小的二进制文件,因此协议缓冲区可能是一个可行的选择-同时提供版本公差和可移植性。不同于BinaryFormatter,协议缓冲区连线格式并不包含所有类型的元数据。只是非常简洁的标记来识别数据。

在.NET中,有一些实现。特别是

我虚心地争辩说,protobuf-net(我写的)允许对典型C#类使用更多的.NET惯用用法(“常规”协议缓冲区往往要求代码生成)。例如:

[ProtoContract]
public class Person {
   [ProtoMember(1)]
   public int Id {get;set;}
   [ProtoMember(2)]
   public string Name {get;set;}
}
....
Person person = new Person { Id = 123, Name = "abc" };
Serializer.Serialize(destStream, person);
...
Person anotherPerson = Serializer.Deserialize<Person>(sourceStream);

1
即使“简短标记”仍然是元数据。我对OP想要什么的理解不过是对象中的数据。因此,例如,如果对象是具有2个32位整数的结构,则他希望结果是8个字节的字节数组。
user316117 2013年

@ user316117,这对于版本控制确实是一个痛苦。每种方法都有优点和缺点。
Marc Gravell


有一种避免使用Proto *属性的方法?我要使用的实体在第三方库中。
亚历75年

5

这对我有用:

byte[] bfoo = (byte[])foo;

foo 是100%确定的字节数组对象。


2

看一下序列化,这是一种将整个对象“转换”为字节流的技术。您可以将其发送到网络或将其写入文件,然后稍后将其还原回对象。


我认为chuckhlogan明确拒绝了该请求(Formatter == Serialization)。
Henk Holterman

@Henk-这取决于原因是什么;他提到了额外的信息,我将其称为元数据类型和字段信息。您可以使用序列化而无需额外的开销;只是不与BinaryFormatter
马克·格雷夫

2

我找到了另一种将对象转换为byte []的方法,这是我的解决方案:

IEnumerable en = (IEnumerable) myObject;
byte[] myBytes = en.OfType<byte>().ToArray();

问候


1

要直接访问对象的内存(执行“核心转储”),您需要进入不安全的代码。

如果您想要比BinaryWriter更紧凑的东西,或者原始内存转储将为您提供帮助,那么您需要编写一些自定义序列化代码,以从对象中提取关键信息并以最佳方式打包它们。

编辑 PS将BinaryWriter方法包装到DeflateStream中以压缩数据非常容易,这通常会将数据的大小减半。


1
不安全的代码还不够。即使在不安全的代码中,C#和CLR仍然不允许您使用原始指针指向托管对象,也不能将两个对象引用放在一起。
Pavel Minaev 09年

0

我相信您要尝试做的事是不可能的。程序停止后

BinaryFormatter创建的垃圾是从文件中恢复对象所必需的。
但是,可以获取对象数据,您只需要知道对象数据的确切大小(比听起来要难得多):

public static unsafe byte[] Binarize(object obj, int size)
{
    var r = new byte[size];
    var rf = __makeref(obj);
    var a = **(IntPtr**)(&rf);
    Marshal.Copy(a, r, 0, size);
    return res;
}

可以通过以下方法恢复:

public unsafe static dynamic ToObject(byte[] bytes)
{
    var rf = __makeref(bytes);
    **(int**)(&rf) += 8;
    return GCHandle.Alloc(bytes).Target;
}

上述方法无法用于序列化的原因是,返回数据中的前四个字节对应于RuntimeTypeHandle。会RuntimeTypeHandle描述对象的布局/类型,但每次运行程序时其值都会更改。

编辑:那是愚蠢的,不要这样做-> 如果您已经确定要反序列化的对象的类型,可以在反序列化时将这些字节切换BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value)

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.