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

136
我有一组对象需要写入二进制文件。为了使文件中的字节紧凑,我不能使用BinaryFormatter。因为BinaryFormatter为反序列化需求添加了各种信息。
如果我尝试使用:
byte[] myBytes = (byte[]) myObject 

我遇到了一个运行时异常。

我希望这个过程能够快速进行,所以我不想复制字节数组。我只想让强制转换 byte[] myBytes = (byte[]) myObject 正常工作!

好的,为了明确起见,我不能在输出文件中有 任何 元数据,只能包含对象字节。根据所得到的答案,看起来我需要编写低级别的 Buffer.BlockCopy 代码,可能需要使用不安全代码。

16个回答

225
将一个对象转换为字节数组的方法:
// 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]属性即可启用序列化


12
我尝试了这个方法,它添加了各种元数据。原帖作者说他不想要元数据。 - user316117
5
更不用说,每个人似乎都认为你尝试序列化的是你自己编写的内容,或者已经预先设置好要序列化的内容。 - Hexum064
3
在第二个代码示例中,你可以直接将字节数组传递给MemoryStream的构造函数。这样可以省略使用Write(...)Seek(...)的步骤。 - unknown6656
15
现在被视为不安全的是二进制格式化程序(binary formatter)的使用。 https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide#preferred-alternatives - uniquelau
我正在使用一个由第三方供应商标记为密封的pdf对象进行工作。因此,我无法将该类标记为[Serializable]。 - randow_user_1234
显示剩余2条评论

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

例如:

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

}

我有几个整数和几个字符串要写入,怎么办? - Smith
2
@Smith:是的,你可以这样做,只需将它们按顺序写在一起。BinaryWriter将以BinaryReader可以读取的格式进行编写,只要你按相同的顺序写入和读取即可。 - Guffa
1
BinaryWriter/Reader和使用BinaryFormatter有什么区别? - Smith
4
使用 BinaryWriter/Reader 进行序列化和反序列化,可以自己处理,并且只写入/读取绝对需要的数据,使其尽可能紧凑。 BinaryFormatter 使用反射来查找要写入/读取的数据,并使用适用于所有可能情况的格式。 它还在流中包含有关格式的元信息,因此增加了更多开销。 - Guffa
2
@Smith:你可以将枚举转换为 int(或者如果你已经指定了其他类型作为枚举的存储方式),然后写入。当你读取它时,你可以将其转换为枚举类型。 - Guffa
显示剩余2条评论

32

myObject转换为byte[]是永远不可能成功的,除非你有一个明确的转换方法,或者myObject本身就是一个byte[]。你需要一种序列化框架来实现这个功能。有很多框架可供选择,包括我比较喜欢的Protocol Buffers。它在空间和时间方面都非常高效。

然而,几乎所有的序列化框架都有严格的限制,特别是跨平台的Protocol Buffers更是如此。

如果您能提供更多的要求,我们可以提供更多的帮助。但是,实现序列化永远不会像强制转换那么简单...

编辑:回应以下内容:

我需要我的二进制文件仅包含对象的字节。仅仅是字节,没有任何元数据。对象之间紧密相连。因此,我将实现自定义序列化。

请记住,对象中的字节经常是引用...所以您需要想好如何处理它们。

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

个人建议是,如果您只需要为几种特定类型实现此功能,则不要试图开发通用的序列化框架。只需在所有需要的类型中实现一个实例方法和一个静态方法即可:

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

需要记住一件事:如果涉及到继承,所有的事情都会变得更加复杂。如果没有继承,如果你知道你所要处理的类型,就不需要包含任何类型信息。当然,还有版本控制的问题 - 你是否需要担心不同版本的类型之间的向前和向后兼容性?


我应该更准确地称之为“protobuf-csharp-port”(Google-code),还是“dotnet-protobufs”(Git)? - Marc Gravell
1
我需要我的二进制文件包含对象的字节。仅仅是字节,没有任何元数据。对象与对象之间紧密相连。因此,我将实现自定义序列化。 - chuckhlogan
6
“零”元数据的风险在于,它非常无法与版本兼容,因为它很少有机会在太晚之前允许灵活性。协议缓冲区具有相当高的数据密度。你真的需要那额外的转动螺丝吗? - Marc Gravell
@Marc:当然,对于整数而言,PB 可能比原始字节更密集... - Jon Skeet

26

使用二进制格式化程序现在被认为是不安全的。请参见 --> Docs Microsoft

只需使用 System.Text.Json:

将对象序列化为字节:

JsonSerializer.SerializeToUtf8Bytes(obj);

将字节数组反序列化为您的类型:

JsonSerializer.Deserialize(byteArray);


似乎 .Net Framework 并没有提供 System.Text.Json。 - anatol
从 .NET 5 开始,这是推荐的做法。 - Jawand Singh
System.Text.Json包可从.NET Core 3.1获取。 - saad bin sami

23
我将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
1
不应使用此链接:https://learn.microsoft.com/en-gb/dotnet/standard/serialization/binaryformatter-security-guide - johnstaveley

14

您正在讨论序列化,它可以采用许多形式。如果您想要小且二进制的数据格式,那么协议缓冲可能是一个可行的选择-同时具有版本容忍性和可移植性。与 BinaryFormatter 不同,协议缓冲线路格式不包括所有类型元数据;只包含非常简洁的标记来识别数据。

.NET中有一些实现;特别是:

我谦虚地认为,protobuf-net(由我编写)允许使用典型的C#类进行更多的 .NET 惯用用法(“常规”协议缓冲 tend to demand 代码生成);例如:

[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
@user316117 这对版本控制来说真是一个大麻烦。每种方法都有其优缺点。 - Marc Gravell
如何在protobuf-csharp-port和protobuf-net之间进行选择 - Timeless
有没有避免使用Proto*属性的方法? 我想使用的实体在第三方库中。 - Alex 75

4
这对我有用:

这对我有用:

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

foo 是一个对象,我百分之百确定它是一个字节数组。


4
我发现最好的方法是使用Newtonsoft.Json。该方法对我有效。请使用Newtonsoft.Json
public TData ByteToObj<TData>(byte[] arr){
                    return JsonConvert.DeserializeObject<TData>(Encoding.UTF8.GetString(arr));
    }

public byte[] ObjToByte<TData>(TData data){
            var json = JsonConvert.SerializeObject(data);
            return Encoding.UTF8.GetBytes(json);
}

2

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


我认为chuckhlogan明确拒绝了(Formatter == Serialization)。 - H H
@Henk - 这取决于原因是什么;他提到了额外的信息,我认为这是类型元数据和字段信息;您可以使用序列化而不带有这种开销;只是不能使用BinaryFormatter - Marc Gravell

1

该方法从一个对象返回一个字节数组。

private byte[] ConvertBody(object model)
        {
            return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model));
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接