Chuyển đổi bất kỳ đối tượng thành một byte []


138

Tôi đang viết một kết nối TCP nguyên mẫu và tôi gặp một số khó khăn khi đồng nhất hóa dữ liệu được gửi.

Hiện tại, tôi không gửi gì ngoài chuỗi, nhưng trong tương lai chúng tôi muốn có thể gửi bất kỳ đối tượng nào.

Mã hiện tại khá đơn giản, vì tôi nghĩ mọi thứ có thể được chuyển thành một mảng byte:

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[]'.
  ...
}

Điều này tất nhiên là đủ dễ dàng giải quyết với một

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

Vấn đề là, nếu tôi làm theo cách đó, tôi cần kiểm tra MỌI loại đối tượng không thể chuyển thành byte [] khi chạy.

Vì tôi không biết mọi đối tượng không thể chuyển thành byte [] khi chạy, đây thực sự không phải là một tùy chọn.

Làm thế nào để chuyển đổi bất kỳ đối tượng nào thành một mảng byte trong C # .NET 4.0?


2
Điều này là không thể theo bất kỳ cách có ý nghĩa nào nói chung (ví dụ, xem xét một ví dụ FileStreamhoặc bất kỳ đối tượng nào đóng gói một tay cầm như thế).
jason

2
Bạn có dự định có tất cả các máy khách đang chạy .NET không? Nếu câu trả lời là không, bạn nên xem xét một số hình thức tuần tự hóa khác (XML, JSON hoặc các lượt thích)
R. Martinho Fernandes

Câu trả lời:


195

Sử dụng 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();
    }
}

Lưu ý rằng objvà bất kỳ thuộc tính / trường nào trong obj(và tương tự cho tất cả các thuộc tính / trường của chúng) đều sẽ cần phải được gắn thẻ với Serializablethuộc tính để được nối tiếp thành công với điều này.


13
Hãy cẩn thận với những gì bạn làm với đối tượng "bất kỳ" ở phía bên kia, vì nó có thể không còn ý nghĩa nữa (ví dụ: nếu đối tượng đó là một tay cầm cho một tập tin, hoặc tương tự)
Rowland Shaw

1
Yup, hãy cẩn thận bình thường, nhưng đó không phải là một ý tưởng tồi để nhắc nhở mọi người về họ.
Daniel DiPaolo

24
Có thể là một ý tưởng tốt để kết hợp việc sử dụng MemoryStream trong một usingkhối, vì nó sẽ háo hức giải phóng bộ đệm nội bộ được sử dụng.
R. Martinho Fernandes

1
Là phương pháp .NET bị ràng buộc? Tôi có thể tuần tự hóa một cấu trúc C với StructLayoutAtrribution và gửi qua socket tới mã C và mong rằng mã C hiểu được cấu trúc đó? Tôi đoán là không?
joe

103

kiểm tra bài viết này: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Sử dụng mã dưới đây

// 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
Như đã đề cập trong một bình luận cho câu trả lời này , MemorySteamnên được gói trong một usingkhối.
tân binh1024

Có bất cứ điều gì tôi phải tôn trọng trong adition? Tôi đã triển khai nó theo cách đó và Định dạng một Đối tượng chứa 3 thành viên cộng đồng int32 dẫn đến một ByteArray dài 244 Byte. Tôi không biết gì về cú pháp C # hay có bất cứ điều gì tôi sẽ bỏ lỡ khi sử dụng không?
dhein

Xin lỗi, tôi không thể có được vấn đề của bạn. Bạn có thể gửi mã?
kombsh

@kombsh Tôi thử ở dạng ngắn: [Nối tiếp] lớp GameConfiguration {công khai map_options_t enumMapIndex; công khai Int32 iPlayerAmount; riêng tư Int32 iGameID; } byte [] baPquet; GameConfiguration objGameConfClient = new GameConfiguration (); baPquet = BinModler.ObjectToByteArray (objGameConfClient); Bây giờ baPquet chứa khoảng 244 Byte f nội dung. Tôi dự kiến ​​12.
dhein

1
@kombsh bạn có thể loại bỏ rõ ràng các đối tượng dùng một lần trong ví dụ của bạn.
Rudolf Dvoracek

29

Giống như những người khác đã nói trước đây, bạn có thể sử dụng tuần tự nhị phân, nhưng nó có thể tạo ra một byte bổ sung hoặc được giải tuần tự hóa thành một đối tượng không có cùng dữ liệu. Sử dụng sự phản chiếu mặt khác là khá phức tạp và rất chậm. Có một giải pháp khác có thể chuyển đổi nghiêm ngặt các đối tượng của bạn thành byte và vise-Versa - marshalling:

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

Và để chuyển đổi byte thành đối tượng:

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

Việc sử dụng phương pháp này cho các đối tượng và cấu trúc nhỏ so với trường tuần tự hóa của bạn theo trường (do sao chép hai lần từ / sang bộ nhớ không được quản lý), nhưng cách dễ nhất là chuyển đổi đối tượng thành byte [] mà không thực hiện tuần tự hóa và không có thuộc tính [Nối tiếp].


1
Tại sao bạn nghĩ StructureToPtr+ Copylà chậm? Làm thế nào nó có thể chậm hơn nối tiếp? Có giải pháp nào nhanh hơn không?
Anton Samsonov

Nếu bạn sử dụng nó cho các cấu trúc nhỏ bao gồm một vài loại đơn giản, vâng (đó là trường hợp khá phổ biến), thì sẽ chậm vì sao chép và sao chép bốn lần (từ đối tượng sang heap, từ heap sang byte, từ byte sang heap, từ heap để phản đối). Nó có thể nhanh hơn khi IntPtr được sử dụng thay vì byte, nhưng không phải trong trường hợp này. Và nó nhanh hơn đối với các kiểu như vậy để viết serializer riêng mà chỉ cần đặt các giá trị vào mảng byte. Tôi không nói rằng nó chậm hơn so với tuần tự hóa tích hợp cũng không phải là "rất chậm chết tiệt".
Aberro

1
Tôi thích phương pháp này vì nó ánh xạ từng byte. Đây là một phương pháp thực sự tốt để trao đổi bộ nhớ với ánh xạ C ++. +1 cho bạn.
Hao Nguyen

2
Lưu ý cho người dùng tiềm năng, mặc dù rất thông minh, câu trả lời này không hoạt động trên mảng cấu trúc, các đối tượng không thể được sắp xếp thành cấu trúc hoặc đối tượng không được quản lý có bố mẹ ComVisible (false) trong hệ thống phân cấp của chúng.
TernaryToperator

1
Để giải trừ làm thế nào bạn có được "kích thước"? trongvar bytes = new byte[size];
Ricardo

13

Những gì bạn đang tìm kiếm là serialization. Có một số hình thức tuần tự hóa có sẵn cho nền tảng .Net


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

Bạn có thể sử dụng nó như mã dưới đây.

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

Sử dụng Encoding.UTF8.GetBytesnhanh hơn sử dụng MemoryStream. Ở đây, tôi đang sử dụng NewtonsoftJson để chuyển đổi đối tượng đầu vào thành chuỗi JSON và sau đó nhận byte từ chuỗi JSON.

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

Điểm chuẩn cho phiên bản của @Daniel DiPaolo với phiên bản này

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

Giải pháp kết hợp trong lớp Tiện ích mở rộng:

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

Bạn có thể sử dụng các công cụ tuần tự hóa tích hợp trong khung và tuần tự hóa thành MemoryStream . Đây có thể là tùy chọn đơn giản nhất, nhưng có thể tạo ra một byte lớn hơn [] có thể rất cần thiết cho kịch bản của bạn.

Nếu đó là trường hợp, bạn có thể sử dụng sự phản chiếu để lặp lại các trường và / hoặc các thuộc tính trong đối tượng được tuần tự hóa và viết chúng theo cách thủ công vào MemoryStream, gọi tuần tự theo cách đệ quy nếu cần để tuần tự hóa các loại không tầm thường. Phương pháp này phức tạp hơn và sẽ mất nhiều thời gian hơn để thực hiện, nhưng cho phép bạn kiểm soát nhiều hơn đối với luồng được tuần tự hóa.


1

Làm thế nào về một cái gì đó đơn giản như thế này?

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

1

Tôi thà sử dụng biểu thức "tuần tự hóa" hơn là "truyền thành byte". Tuần tự hóa một đối tượng có nghĩa là chuyển đổi nó thành một mảng byte (hoặc XML hoặc một cái gì khác) có thể được sử dụng trên hộp từ xa để xây dựng lại đối tượng. Trong .NET, các Serializablethuộc tính đánh dấu các loại có đối tượng có thể được tuần tự hóa.


1

Cách khác để chuyển đổi đối tượng thành mảng byte:

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

Đã thử điều này, nó dường như không hoạt động với tôi trên .NET 4.6.1 và Windows 10.
Contango

0

Một triển khai bổ sung, sử dụng JSON nhị phân Newtonsoft.Json và không yêu cầu đánh dấu mọi thứ bằng thuộc tính [Nối tiếp]. Chỉ có một nhược điểm là một đối tượng phải được bọc trong lớp ẩn danh, do đó mảng byte thu được với tuần tự nhị phân có thể khác với đối tượng này.

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

Lớp ẩn danh được sử dụng vì BSON nên bắt đầu bằng một lớp hoặc mảng. Tôi đã không cố gắng khử tuần tự byte [] trở lại đối tượng và không chắc nó có hoạt động không, nhưng đã kiểm tra tốc độ chuyển đổi thành byte [] và nó hoàn toàn đáp ứng nhu cầu của tôi.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.