C # 'da bir nesneyi bayt dizisine dönüştürme


102

İkili bir dosyaya yazmam gereken nesneler koleksiyonum var.

Dosyadaki baytların kompakt olmasına ihtiyacım var, bu yüzden kullanamam BinaryFormatter. BinaryFormatterseriyi kaldırma ihtiyaçları için her türlü bilgiyi atar.

Denersem

byte[] myBytes = (byte[]) myObject 

Bir çalışma zamanı istisnası alıyorum.

Bunun hızlı olmasına ihtiyacım var, bu yüzden etrafındaki bayt dizilerini kopyalamak istemiyorum. Sadece oyuncu kadrosunun byte[] myBytes = (byte[]) myObjectçalışmasını istiyorum!

Tamam, açık olmak gerekirse, çıktı dosyasında herhangi bir meta veriye sahip olamıyorum . Sadece nesne baytları. Paketlenmiş nesneden nesneye. Alınan cevaplara göre, düşük seviyeli Buffer.BlockCopykod yazacağım gibi görünüyor . Belki güvenli olmayan kod kullanıyor.

Yanıtlar:


181

Bir nesneyi bayt dizisine dönüştürmek için:

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

Bu işlevi kodunuza kopyalamanız ve ona bir bayt dizisine dönüştürmeniz gereken nesneyi göndermeniz yeterlidir. Bayt dizisini tekrar bir nesneye dönüştürmeniz gerekirse, aşağıdaki işlevi kullanabilirsiniz:

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

Bu işlevleri özel sınıflarla kullanabilirsiniz. [Serializable]Serileştirmeyi etkinleştirmek için yalnızca sınıfınıza öznitelik eklemeniz gerekir


9
Bunu denedim ve her türlü meta veriyi ekledi. OP, meta verileri istemediğini söyledi.
user316117

4
Bahsetmiyorum bile, herkes serileştirmeye çalıştığınız şeyin sizin yazdığınız bir şey olduğunu veya serileştirilmek üzere önceden ayarlandığını varsayıyor gibi görünüyor.
Hexum064

3
Bayt dizisini MemoryStreamikinci kod örneğindeki kurucuya doğrudan iletebilirsiniz . Bu kullanımını ortadan kaldıracak Write(...)ve Seek(...).
unknown6656

42

Serileştirilmiş verilerin gerçekten kompakt olmasını istiyorsanız, serileştirme yöntemlerini kendiniz yazabilirsiniz. Bu şekilde minimum ek yükünüz olur.

Misal:

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

}

ne yazacağım birkaç ints ve birkaç string var?
Smith

1
@Smith: Evet, bunu yapabilirsiniz, sadece birbiri ardına yazın. BinaryWriterO bir biçimde bunları yazacak BinaryReadersürece yazmak ve aynı sırayla okurken, okuyabilir.
Guffa

1
BinaryWriter/ReaderaBinaryFormatter
Smith

3
@Smith: Kullanarak BinaryWriter/Readerserileştirme / seriyi kaldırma işlemini kendiniz yapın ve yalnızca kesinlikle gerekli olan verileri olabildiğince kompakt bir şekilde yazabilir / okuyabilirsiniz. BinaryFormatterKullanımları yansıma yazma / okuma Hangi verileri bulmak ve bir biçimi kullanan bu olası tüm durumlar için çalışır. Ayrıca akıştaki formatla ilgili meta bilgileri de içerir, böylece daha fazla ek yük ekler.
Guffa

1
@Smith: Numaralamayı çevirebilir int(veya sıralama için depolama alanı olarak başka bir tür belirlediyseniz) ve yazabilirsiniz. Okuduğunuzda enum türüne çevirebilirsiniz.
Guffa

31

Eh bir döküm myObjectiçin byte[]siz ya eğer açık bir dönüşüm var sürece asla işe gidiyor myObject olduğunu bir byte[]. Sen bir seri hale çerçeveye ihtiyaç bazı tür. Protokol Tamponları dahil olmak üzere pek çok şey varBana yakın ve benim için değerli olan da şey var. Hem uzay hem de zaman açısından oldukça "zayıf ve kötü".

Hemen hemen tüm serileştirme çerçevelerinin, neyi serileştirebileceğiniz konusunda önemli kısıtlamalara sahip olduğunu göreceksiniz - Platformlar arası olması nedeniyle bazılarından daha fazla Protokol Tamponları.

Daha fazla gereksinim belirtebilirseniz, size daha fazla yardımcı olabiliriz - ancak bu asla oyuncu seçimi kadar basit olmayacak ...

DÜZENLEME: Sadece buna yanıt vermek için:

Nesnenin baytlarını içermesi için ikili dosyama ihtiyacım var. Yalnızca baytlar, hiçbir meta veri yok. Paketlenmiş nesneden nesneye. Bu yüzden özel serileştirme uygulayacağım.

Lütfen nesnelerinizdeki baytların oldukça sık referans olduğunu unutmayın ... bu yüzden onlarla ne yapacağınızı bulmanız gerekecek.

Kendi özel serileştirme çerçevenizi tasarlamanın ve uygulamanın hayal ettiğinizden daha zor olduğunu göreceksiniz.

Kişisel olarak tavsiye ederim, eğer bunu sadece birkaç özel tip için yapmanız gerekiyorsa, genel bir serileştirme çerçevesi bulmaya çalışmakla uğraşmayın. İhtiyacınız olan tüm türlerde yalnızca bir örnek yöntemi ve statik bir yöntem uygulayın:

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

Unutulmaması gereken bir şey var: Kalıtım varsa, her şey daha karmaşık hale gelir. Kalıtım olmadan, hangi türle başladığınızı biliyorsanız, herhangi bir tür bilgisi eklemenize gerek yoktur. Elbette, sürüm oluşturma meselesi de var - türlerinizin farklı sürümleriyle geriye ve ileriye dönük uyumluluk konusunda endişelenmeniz gerekiyor mu?


Bundan "protobuf-csharp-port" (Google kodu) veya "dotnet-protobufs" (Git) olarak bahsetmek benim için daha doğru mu?
Marc Gravell

1
Nesnenin baytlarını içermesi için ikili dosyama ihtiyacım var. Yalnızca baytlar, hiçbir meta veri yok. Paketlenmiş nesneden nesneye. Bu yüzden özel serileştirme uygulayacağım.
chuckhlogan

6
Sıfır meta veri riski , çok geç olmadan esnekliğe izin vermenin çok az yolu olduğundan, o zaman çok sürüm toleranssız olmanızdır . Protokol arabellekleri oldukça veri yoğunlukludur. Gerçekten vidanın o ekstra dönüşüne ihtiyacın var mı?
Marc Gravell

@Marc: Ve tabii ki tamsayılar için, PB ham baytlardan daha yoğun olabilir ...
Jon Skeet

17

Crystalonics'in cevabını aldım ve bunları genişletme yöntemlerine dönüştürdüm. Umarım başka biri onları faydalı bulacaktır:

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
Bu gerçekten kullanışlı ve kolay !! Teşekkür ederim.
MrHIDEn

13

Gerçekten pek çok biçimde alabilen serileştirmeden bahsediyorsunuz. Küçük ve ikili istediğiniz için, protokol arabellekleri uygun bir seçenek olabilir - sürüm toleransı ve taşınabilirlik de sağlar. Bunun aksine BinaryFormatter, protokol arabellek kablo biçimi tüm tür meta verilerini içermez; verileri tanımlamak için yalnızca çok kısa işaretler.

.NET'te birkaç uygulama vardır; özellikle

Ben naçizane iddia ediyorum o protobuf-net (Yazdığım olan) tipik C # sınıfları ( "normal" protokol tamponlar eğilimindedir daha .NET-deyimsel kullanımına izin verir talep kodu nesil); Örneğin:

[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
"Kısa işaretçiler" bile meta verilerdir. OP'nin ne istediğine dair anlayışım, nesnedeki verilerden başka bir şey değildi. Bu nedenle, örneğin, nesne 2 32 bitlik tamsayılı bir yapı olsaydı, sonucun 8 baytlık bir bayt dizisi olmasını beklerdi.
user316117

@ user316117, bu da sürüm oluşturma için gerçek bir acı. Her yaklaşımın avantajları ve dezavantajları vardır.
Marc Gravell


Proto * niteliklerini kullanmaktan kaçınmanın bir yolu var mı? Kullanmak istediğim varlıklar bir 3. taraf kitaplığında.
Alex 75

5

Bu benim için çalıştı:

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

foo bayt dizisi olduğundan% 100 emin olduğum bir Nesnedir.


2

Bir nesnenin tamamını bir bayt akışına "dönüştürme" tekniği olan Serileştirmeye bir göz atın . Bunu ağa gönderebilir veya bir dosyaya yazabilir ve daha sonra bir nesneye geri yükleyebilirsiniz.


Sanırım chuckhlogan bunu açıkça reddetti (Formatter == Serileştirme).
Henk Holterman

@Henk - nedenlere bağlı; metadata ve alan bilgisi olarak kabul ettiğim fazladan bilgiden bahsetti; serileştirmeyi ek yük olmadan kullanabilirsiniz; sadece değil BinaryFormatter.
Marc Gravell

2

Bir nesneyi bayta [] dönüştürmenin başka bir yolunu buldum, işte çözümüm:

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

Saygılarımızla


1

Bir nesnenin belleğine doğrudan erişmek için (bir "çekirdek dökümü" yapmak için) güvenli olmayan koda gitmeniz gerekir.

BinaryWriter veya ham bellek dökümünden daha kompakt bir şey istiyorsanız, nesneden kritik bilgileri çıkaran ve en uygun şekilde paketleyen bazı özel serileştirme kodu yazmanız gerekir.

düzenleme PS Verileri sıkıştırmak için BinaryWriter yaklaşımını DeflateStream'e sarmak çok kolaydır, bu genellikle verilerin boyutunu kabaca yarıya indirir.


1
Güvenli olmayan kod yeterli değildir. C # ve CLR, güvenli olmayan kodda bile yönetilen bir nesneye ham bir işaretçi almanıza veya bir birleşimde iki nesne başvurusu koymanıza izin vermez.
Pavel Minaev

1

Yapmaya çalıştığınız şeyin imkansız olduğuna inanıyorum.

Oluşturulan önemsiz, BinaryFormatterprogramınız durduktan sonra nesneyi dosyadan kurtarmak için gereklidir.
Bununla birlikte, nesne verilerini elde etmek mümkündür, sadece tam boyutunu bilmeniz gerekir (göründüğünden daha zor):

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

bu şu yolla kurtarılabilir:

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

Yukarıdaki yöntemlerin serileştirme için çalışmamasının nedeni, döndürülen verilerdeki ilk dört baytın bir RuntimeTypeHandle. RuntimeTypeHandleNesnenin düzen / türünü açıklar ama bunun değeri programı koştu olan her zaman değiştirir.

DÜZENLEME: aptalca, bunu yapmayın -> Seri dışı bırakılacak nesnenin türünü zaten biliyorsanız, seriyi kaldırma sırasında bu baytları değiştirebilirsiniz BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.