แปลงวัตถุใด ๆ เป็นไบต์ []


143

ฉันกำลังเขียนการเชื่อมต่อ 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 ){...}

ปัญหาคือถ้าฉันทำแบบนั้นฉันต้องตรวจสอบวัตถุทุกประเภทที่ไม่สามารถส่งเป็นไบต์ [] ในรันไทม์ได้

เนื่องจากฉันไม่ทราบว่าทุกออบเจ็กต์ที่ไม่สามารถส่งเป็นไบต์ [] ในรันไทม์นี่ไม่ใช่ตัวเลือก

หนึ่งจะแปลงวัตถุใด ๆ ให้เป็นอาร์เรย์ไบต์ใน C # .NET 4.0 ได้อย่างไร


2
สิ่งนี้เป็นไปไม่ได้ในทางที่มีความหมายโดยทั่วไป (พิจารณาตัวอย่างเช่นตัวอย่างของFileStreamหรือวัตถุใด ๆ ที่ห่อหุ้มที่จับเช่นนั้น)
สัน

2
คุณต้องการให้ไคลเอนต์ทั้งหมดใช้งาน. NET หรือไม่? หากคำตอบคือไม่คุณควรพิจารณารูปแบบอื่น ๆ ของการทำให้เป็นอนุกรม (XML, JSON หรือสิ่งที่ชอบ)
R.Martinho Fernandes

คำตอบ:


202

ใช้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

24
อาจเป็นความคิดที่ดีที่จะรวมการใช้ MemoryStream ไว้ในusingบล็อกเนื่องจากจะปล่อยบัฟเฟอร์ภายในที่ใช้อย่างกระตือรือร้น
R.Martinho Fernandes

1
วิธีนี้. NET ถูกผูกไว้หรือไม่? ฉันสามารถต่ออนุกรมโครงสร้าง C ด้วย StructLayoutAtrribute และส่งผ่านซ็อกเก็ตไปยังรหัส C และคาดว่ารหัส C เข้าใจโครงสร้างได้หรือไม่ ฉันเดาว่าไม่?
โจ

104

ชำระเงินบทความนี้: 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;
}

11
ดังกล่าวในความคิดเห็นที่คำตอบนี้ที่MemorySteamควรจะห่อในusingบล็อก
rookie1024

มีสิ่งใดที่ฉันต้องเคารพในการยกย่อง? ฉันใช้วิธีนี้และการจัดรูปแบบวัตถุที่มีสมาชิกสาธารณะ 3 int32 ส่งผลให้ไบต์อาเรย์ยาว 244 ไบต์ ฉันไม่รู้อะไรบางอย่างเกี่ยวกับไวยากรณ์ C # หรือมีอะไรที่ฉันจะพลาดใช้หรือไม่?
dhein

ขอโทษค่ะฉันไม่เข้าใจปัญหาของคุณ คุณสามารถโพสต์รหัส?
kombsh

@kombsh ฉันลองใช้แบบสั้น ๆ : คลาส GameConfiguration [ต่อเนื่องได้] {public map_options_t enumMapIndex; สาธารณะ Int32 iPlayerAmount; ส่วนตัว Int32 iGameID; } ไบต์ [] baPacket; GameConfiguration objGameConfClient = GameConfiguration ใหม่ (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); ตอนนี้ baPacket มีเนื้อหาประมาณ 244 ไบต์ f ฉันคาดว่า 12.
dhein

1
@kombsh คุณสามารถทิ้งวัตถุที่ใช้แล้วทิ้งในตัวอย่างของคุณได้อย่างชัดเจน
Rudolf Dvoracek

32

เช่นเดียวกับคนอื่น ๆ ได้กล่าวไว้ก่อนหน้านี้คุณสามารถใช้การทำให้เป็นอนุกรมไบนารีได้ แต่อาจสร้างไบต์พิเศษหรือถูก deserialized ลงในวัตถุที่มีข้อมูลไม่เหมือนกันทุกประการ ในทางกลับกันการใช้การสะท้อนกลับค่อนข้างซับซ้อนและช้ามาก มีวิธีแก้ปัญหาอื่นที่สามารถแปลงวัตถุของคุณเป็นไบต์และหนีบในทางกลับกันได้อย่างเคร่งครัด - 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);

และในการแปลงไบต์เป็นวัตถุ:

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]


1
ทำไมคิดStructureToPtr+ Copyช้าจัง จะช้ากว่าการทำให้เป็นอนุกรมได้อย่างไร? มีวิธีแก้ปัญหาที่เร็วกว่านี้หรือไม่?
Anton Samsonov

หากคุณใช้สำหรับโครงสร้างขนาดเล็กที่ประกอบด้วยประเภทที่เรียบง่ายไม่กี่ประเภทใช่ (ซึ่งเป็นกรณีทั่วไป) มันทำงานช้าเนื่องจากการคัดลอกแบบมาร์แชลลิ่งและรูปสี่เหลี่ยม (จากวัตถุไปยังฮีปจากฮีปเป็นไบต์จากไบต์ถึงฮีปจากฮีป เพื่อคัดค้าน) อาจเร็วกว่าเมื่อใช้ IntPtr แทนไบต์ แต่ไม่ใช่ในกรณีนี้ และเร็วกว่าสำหรับประเภทดังกล่าวในการเขียน serializer ของตัวเองซึ่งเพียงแค่ใส่ค่าลงในไบต์อาร์เรย์ ฉันไม่ได้บอกว่ามันช้ากว่าการทำให้เป็นอนุกรมในตัวหรือว่ามัน "ช้ามาก"
เบอร์โร

1
ฉันชอบวิธีนี้เนื่องจากมันแมปไบต์ต่อไบต์ นี่เป็นวิธีที่ดีมากในการแลกเปลี่ยนหน่วยความจำด้วยการแมป C ++ +1 สำหรับคุณ
Hao Nguyen

2
หมายเหตุสำหรับผู้มีโอกาสเป็นผู้ใช้คำตอบนี้ใช้ไม่ได้กับอาร์เรย์โครงสร้างวัตถุที่ไม่สามารถจัดระเบียบเป็นโครงสร้างที่ไม่มีการจัดการหรืออ็อบเจ็กต์ที่มีพาเรนต์ ComVisible (เท็จ) ในลำดับชั้น
TernaryTopiary

1
หากต้องการทราบว่าคุณได้รับ "ขนาด" มาอย่างไร? ในvar bytes = new byte[size];
Ricardo

14

สิ่งที่คุณกำลังมองหาคือการทำให้เป็นอนุกรม มีหลายรูปแบบของการทำให้เป็นอนุกรมสำหรับแพลตฟอร์ม. 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;
    }
}

คุณสามารถใช้ได้เหมือนโค้ดด้านล่าง

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

โซลูชั่นรวมในคลาส Extensions:

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 นี่อาจเป็นตัวเลือกที่ตรงไปตรงมาที่สุด แต่อาจสร้างไบต์ [] ที่ใหญ่กว่าที่จำเป็นสำหรับสถานการณ์ของคุณ

หากเป็นเช่นนั้นคุณสามารถใช้การสะท้อนเพื่อทำซ้ำในฟิลด์และ / หรือคุณสมบัติในออบเจ็กต์ที่จะทำให้เป็นอนุกรมและเขียนลงใน MemoryStream ด้วยตนเองเรียกการทำให้เป็นอนุกรมแบบวนซ้ำหากจำเป็นเพื่อทำให้เป็นอนุกรมประเภทที่ไม่สำคัญ วิธีนี้ซับซ้อนกว่าและจะใช้เวลานานกว่าในการนำไปใช้ แต่ช่วยให้คุณควบคุมสตรีมแบบอนุกรมได้มากขึ้น



1

ฉันควรใช้นิพจน์ "serialization" มากกว่า "การแคสต์เป็นไบต์" การทำให้วัตถุเป็นอนุกรมหมายถึงการแปลงเป็นอาร์เรย์ไบต์ (หรือ 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 binary 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 ควรเริ่มต้นด้วยคลาสหรืออาร์เรย์ ฉันไม่ได้พยายาม deserialize byte [] กลับไปที่ object และไม่แน่ใจว่ามันใช้งานได้หรือไม่ แต่ได้ทดสอบความเร็วของการแปลงเป็น byte [] และมันตอบสนองความต้องการของฉันได้อย่างสมบูรณ์


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.