วิธีการแปลงวัตถุเป็นอาร์เรย์ไบต์ใน C #


102

ฉันมีคอลเล็กชันของอ็อบเจ็กต์ที่ต้องเขียนลงในไฟล์ไบนารี

BinaryFormatterฉันต้องไบต์ในไฟล์ที่จะมีขนาดกะทัดรัดดังนั้นผมจึงไม่สามารถใช้ BinaryFormatterพ่นข้อมูลทุกประเภทสำหรับความต้องการในการแยกส่วน

ถ้าฉันพยายาม

byte[] myBytes = (byte[]) myObject 

ฉันได้รับข้อยกเว้นรันไทม์

ฉันต้องการให้สิ่งนี้เร็วดังนั้นฉันจึงไม่ควรคัดลอกอาร์เรย์ของไบต์รอบ ๆ ฉันแค่อยากให้นักแสดงbyte[] myBytes = (byte[]) myObjectทำงาน!

ตกลงเพื่อให้ชัดเจนฉันไม่มีข้อมูลเมตาใด ๆในไฟล์เอาต์พุต ไบต์ของวัตถุเท่านั้น บรรจุวัตถุต่อวัตถุ จากคำตอบที่ได้รับดูเหมือนว่าฉันจะเขียนBuffer.BlockCopyโค้ดระดับต่ำ อาจใช้รหัสที่ไม่ปลอดภัย

คำตอบ:


181

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

// 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

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

3
คุณสามารถส่งอาร์เรย์ไบต์ไปยังตัวสร้างได้โดยตรงMemoryStreamในตัวอย่างโค้ดที่สอง นี้จะกำจัดการใช้และWrite(...) Seek(...)
Unknown6656

42

หากคุณต้องการให้ข้อมูลอนุกรมมีขนาดกะทัดรัดคุณสามารถเขียนวิธีการทำให้เป็นอนุกรมได้ด้วยตัวเอง ด้วยวิธีนี้คุณจะมีค่าใช้จ่ายขั้นต่ำ

ตัวอย่าง:

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

}

ฉันมีหลาย ints ที่จะเขียนและหลายสตริง?
Smith

1
@ สมิ ธ : ใช่คุณทำได้เพียงแค่เขียนต่อกัน BinaryWriterจะเขียนพวกเขาในรูปแบบที่ได้BinaryReaderสามารถอ่านตราบเท่าที่คุณเขียนและอ่านได้ในลำดับเดียวกัน
Guffa

1
อะไรคือความแตกต่างระหว่างBinaryWriter/Readerและการใช้ aBinaryFormatter
Smith

3
@Smith: การใช้BinaryWriter/Readerคุณทำ serialization / deserialisation ด้วยตัวคุณเองและคุณสามารถเขียน / อ่านเฉพาะข้อมูลที่จำเป็นอย่างยิ่งโดยมีขนาดกะทัดรัดที่สุด BinaryFormatterสะท้อนการใช้เพื่อหาข้อมูลที่จะเขียน / อ่านและใช้รูปแบบที่งานสำหรับกรณีที่เป็นไปได้ทั้งหมด นอกจากนี้ยังรวมถึงข้อมูลเมตาเกี่ยวกับรูปแบบในสตรีมเพื่อเพิ่มค่าใช้จ่ายมากยิ่งขึ้น
Guffa

1
@Smith: คุณสามารถโยน enum ไปที่int(หรือถ้าคุณระบุประเภทอื่นเป็นที่เก็บของ enum) และเขียนมัน เมื่อคุณอ่านแล้วคุณสามารถส่งเป็นประเภท enum
Guffa

31

นักแสดงจากmyObjectถึงbyte[]จะไม่ทำงานเว้นแต่คุณจะได้รับการแปลงอย่างชัดเจนหรือถ้าmyObject เป็นไฟล์byte[]. คุณต้องมีกรอบการทำให้เป็นอนุกรมบางประเภท มีมากมายรวมทั้ง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

17

ฉันรับคำตอบของ 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

13

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

ใน. NET มีการนำไปใช้งานเล็กน้อย โดยเฉพาะอย่างยิ่ง

ฉันเจียมยืนยันว่า protobuf สุทธิ (ซึ่งผมเขียน) ช่วยให้การใช้งานมากขึ้น .NET-สำนวนที่มีทั่วไปเรียน C # ( "ปกติ" โปรโตคอลบัฟเฟอร์มีแนวโน้มที่จะมีความต้องการรหัสรุ่น); ตัวอย่างเช่น:

[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
แม้แต่ "เครื่องหมาย terse" ก็ยังเป็นข้อมูลเมตา ความเข้าใจของฉันเกี่ยวกับสิ่งที่ OP ต้องการคืออะไรนอกจากข้อมูลในวัตถุ ตัวอย่างเช่นถ้าออบเจ็กต์เป็นโครงสร้างที่มีจำนวนเต็ม 32 บิต 2 ตัวเขาจะคาดหวังว่าผลลัพธ์จะเป็นอาร์เรย์ไบต์ 8 ไบต์
user316117

@ user316117 ซึ่งเป็นความเจ็บปวดอย่างแท้จริงสำหรับการกำหนดเวอร์ชัน แต่ละแนวทางมีข้อดีและข้อเสีย
Marc Gravell


มีวิธีหลีกเลี่ยงการใช้แอตทริบิวต์ Proto * หรือไม่? เอนทิตีที่ฉันต้องการใช้อยู่ในไลบรารีของบุคคลที่สาม
Alex 75

5

สิ่งนี้ใช้ได้ผลสำหรับฉัน:

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

foo เป็นวัตถุที่ฉันมั่นใจ 100% ว่าเป็นอาร์เรย์ไบต์


2

ดูSerializationซึ่งเป็นเทคนิคในการ "แปลง" วัตถุทั้งหมดให้เป็นไบต์สตรีม คุณสามารถส่งไปยังเครือข่ายหรือเขียนลงในไฟล์แล้วเรียกคืนกลับเป็นวัตถุในภายหลัง


ฉันคิดว่า chuckhlogan ปฏิเสธอย่างชัดเจนว่า (Formatter == Serialization)
Henk Holterman

@ เฮ็งค์ - มันขึ้นอยู่กับว่าเหตุผลคืออะไร เขากล่าวถึงข้อมูลเพิ่มเติมซึ่งฉันใช้เป็นข้อมูลเมตาประเภทและข้อมูลฟิลด์ คุณสามารถใช้การทำให้เป็นอนุกรมโดยไม่มีค่าใช้จ่ายนั้น BinaryFormatterเพียงแค่ไม่ได้อยู่กับ
Marc Gravell

2

ฉันพบวิธีอื่นในการแปลงวัตถุเป็นไบต์ [] นี่คือวิธีแก้ปัญหาของฉัน:

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

ความนับถือ


1

ในการเข้าถึงหน่วยความจำของวัตถุโดยตรง (เพื่อทำ "core dump") คุณจะต้องมุ่งหน้าไปยังรหัสที่ไม่ปลอดภัย

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

แก้ไข PS เป็นเรื่องง่ายมากที่จะรวมแนวทาง BinaryWriter ไว้ใน DeflateStream เพื่อบีบอัดข้อมูลซึ่งโดยปกติจะลดขนาดของข้อมูลลงครึ่งหนึ่งโดยประมาณ


1
รหัสที่ไม่ปลอดภัยไม่เพียงพอ C # และ CLR ยังคงไม่ยอมให้คุณนำตัวชี้ดิบไปยังอ็อบเจ็กต์ที่มีการจัดการแม้ว่าจะอยู่ในโค้ดที่ไม่ปลอดภัยก็ตามหรือใส่การอ้างอิงอ็อบเจ็กต์สองรายการในการรวมกัน
Pavel Minaev

1

ฉันเชื่อว่าสิ่งที่คุณพยายามทำนั้นเป็นไปไม่ได้

ขยะที่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อธิบายถึงรูปแบบ / ชนิดของวัตถุ แต่ค่าของมันเปลี่ยนแปลงทุกครั้งที่โปรแกรมจะวิ่ง

แก้ไข: มันโง่อย่าทำอย่างนั้น -> หากคุณทราบประเภทของวัตถุที่จะ deserialized อยู่แล้วคุณสามารถเปลี่ยนไบต์เหล่านั้นได้BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value)ในช่วงเวลาของการ deserialization

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