Herhangi bir nesneyi bayta dönüştür []


138

Prototip bir TCP bağlantısı yazıyorum ve gönderilecek verileri homojenize etmekte bazı sorunlar yaşıyorum.

Şu anda dizelerden başka bir şey göndermiyorum, ancak gelecekte herhangi bir nesneyi gönderebilmek istiyoruz.

Kod şu anda oldukça basit, çünkü her şeyin bir bayt dizisine dönüştürülebileceğini düşündüm:

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

Elbette bu, bir

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

Sorun şu ki, eğer bu şekilde yaparsam, çalışma zamanında bir bayta [] dönüştürülemeyen HER tür nesneyi kontrol etmem gerekir.

Çalışma zamanında bir bayta [] dönüştürülemeyen her nesneyi bilmediğim için, bu gerçekten bir seçenek değil.

C # .NET 4.0'da herhangi bir nesne bir bayt dizisine nasıl dönüştürülür?


2
Bu, genel olarak anlamlı bir şekilde mümkün değildir (örneğin, bir örneği FileStreamveya bunun gibi bir tutamacı içeren herhangi bir nesneyi düşünün).
jason

2
Tüm istemcilerin .NET'i çalıştırmasını istiyor musunuz? Cevap hayırsa, başka bir serileştirme biçimini de düşünmelisiniz (XML, JSON veya benzerleri)
R. Martinho Fernandes

Yanıtlar:


196

Şunları kullanın 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();
    }
}

Bununla başarılı bir şekilde serileştirilebilmesi için, ve objiçindeki tüm özelliklerin / alanların obj(ve tüm özellikleri / alanları için) tümünün Serializableöznitelikle etiketlenmesi gerekeceğini unutmayın .


13
Diğer taraftaki "herhangi" bir nesneyle ne yaptığınıza dikkat edin, çünkü artık bir anlam ifade etmeyebilir (örneğin, bu nesne bir dosyanın tutamacıysa veya benzeri bir
şeyse

1
Evet, normal uyarılar geçerlidir, ancak insanlara bunları hatırlatmak kötü bir fikir değildir.
Daniel DiPaolo

24
usingKullanılan dahili arabelleği hevesle serbest bırakacağından, MemoryStream'in kullanımını bir bloğa sarmak iyi bir fikir olabilir .
R. Martinho Fernandes

1
Bu yöntem .NET'e bağlı mı? StructLayoutAtrribute ile bir C yapısını seri hale getirip soket aracılığıyla bir C koduna gönderebilir ve C kodunun yapıyı anlamasını bekleyebilir miyim? Sanırım hayır?
joe

103

bu makaleye göz atın: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Aşağıdaki kodu kullanın

// 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
Bir açıklamada belirtildiği gibi bu cevap , MemorySteambir sarılmış olmalıdır usingbloğun.
çaylak1024

ek olarak saygı duymam gereken bir şey var mı? Bunu bu şekilde uyguladım ve 3 int32 genel üye içeren bir Nesneyi Biçimlendirmek 244 Bayt uzunluğunda bir ByteArray ile sonuçlandı. C # sözdizimi hakkında bir şey bilmiyor muyum yoksa muhtemelen kullanmayacağım bir şey var mı?
dhein

Maalesef sorununu anlayamıyorum. Kodu gönderebilir misin?
kombsh

@kombsh Kısa biçimde deniyorum: [Serializable] class GameConfiguration {public map_options_t enumMapIndex; genel Int32 iPlayerAmount; özel Int32 iGameID; } bayt [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); BaPacket şimdi yaklaşık 244 Byte f içeriği içerir. Ben jsut 12'yi bekliyordum.
dhein

1
@kombsh, örneğinizdeki tek kullanımlık nesneleri açıkça imha edebilirsiniz.
Rudolf Dvoracek

31

Diğerlerinin daha önce söylediği gibi, ikili serileştirmeyi kullanabilirsiniz, ancak fazladan bir bayt üretebilir veya tam olarak aynı verilere sahip olmayan bir nesnelere serileştirilmemiş hale getirilebilir. Öte yandan yansımayı kullanmak oldukça karmaşık ve çok yavaştır. Nesnelerinizi kesinlikle bayta ve tersi yönde dönüştürebilen başka bir çözüm daha var - sıralama:

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

Ve baytları nesneye dönüştürmek için:

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

Kendi serileştirme alanınıza göre alan bazında (yönetilmeyen bellekten / belleğe çift kopyalama nedeniyle) küçük nesneler ve yapılar için bu yaklaşımı kullanmak fark edilir ölçüde daha yavaştır ve kısmen güvensizdir, ancak serileştirmeyi uygulamadan nesneyi kesinlikle bayta [] dönüştürmenin en kolay yolu ve [Serileştirilebilir] özniteliği olmadan.


1
Neden StructureToPtr+ Copyyavaş olduğunu düşünüyorsun ? Serileştirmeden nasıl daha yavaş olabilir? Daha hızlı bir çözüm var mı?
Anton Samsonov

Birkaç basit türden oluşan küçük yapılar için kullanırsanız, evet (bu oldukça yaygın bir durumdur), sıralama ve dörtlü kopyalama (nesneden yığına, yığından bayta, bayttan öbeğe, yığından öbeğe) nedeniyle yavaştır nesneye). Bayt yerine IntPtr kullanıldığında daha hızlı olabilir, ancak bu durumda değil. Ve bu tür türlerin, değerleri bayt dizisine koyan kendi serileştiricisini yazması daha hızlıdır. Bunun yerleşik serileştirmeden daha yavaş olduğunu veya "çok çok yavaş" olduğunu söylemiyorum.
Aberro

1
Bayt bayt eşlediği için bu yöntemi seviyorum. Bu, C ++ eşleme ile bellek alışverişi yapmak için gerçekten iyi bir yöntemdir. +1 sizin için.
Hao Nguyen

2
Potansiyel kullanıcılara dikkat edin, çok akıllı olsa da, bu cevap yapı dizileri, yönetilmeyen bir yapı olarak sıralanamayan nesneler veya hiyerarşilerinde bir ComVisible (yanlış) ebeveyni olan nesneler üzerinde çalışmaz.
TernaryTopiary

1
"Bedeni" nasıl elde ettiğinizi deserilize etmek için? invar bytes = new byte[size];
Ricardo

13

Aradığınız şey serileştirme. .Net platformu için çeşitli serileştirme biçimleri mevcuttur.


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

Aşağıdaki kod gibi kullanabilirsiniz.

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

Kullanmak Encoding.UTF8.GetBytes, kullanmaktan daha hızlıdır MemoryStream. Burada, girdi nesnesini JSON dizesine dönüştürmek ve ardından JSON dizesinden bayt almak için NewtonsoftJson kullanıyorum .

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

@Daniel DiPaolo'nun bu versiyondaki versiyonu için kıyaslama

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

Uzantı sınıfında Birleşik Çözümler:

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

Çerçevedeki yerleşik serileştirme araçlarını kullanabilir ve bir MemoryStream'e serileştirebilirsiniz . Bu en basit seçenek olabilir, ancak senaryonuz için kesinlikle gerekli olandan daha büyük bir bayt [] üretebilir.

Durum buysa, yansımayı serileştirilecek nesnedeki alanlar ve / veya özellikler üzerinde yinelemek için kullanabilir ve bunları önemsiz olmayan türleri serileştirmek için gerekirse yinelemeli olarak serileştirmeyi çağırarak MemoryStream'e manuel olarak yazabilirsiniz. Bu yöntem daha karmaşıktır ve uygulanması daha fazla zaman alır, ancak serileştirilmiş akış üzerinde çok daha fazla kontrol sahibi olmanızı sağlar.


1

Bunun gibi basit bir şeye ne dersiniz?

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

1

"Baytlara çevirme" yerine "serileştirme" ifadesini kullanmayı tercih ederim. Bir nesneyi serileştirmek, nesneyi yeniden oluşturmak için uzak kutuda kullanılabilecek bir bayt dizisine (veya XML veya başka bir şeye) dönüştürmek anlamına gelir. .NET'te Serializableöznitelik , nesneleri serileştirilebilen türleri işaretler.


1

Nesneyi bayt dizisine dönüştürmenin alternatif yolu:

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

Bunu denedim, benim için .NET 4.6.1 ve Windows 10'da işe yaramadı.
Contango

0

Newtonsoft.Json ikili JSON kullanan ve her şeyi [Serializable] özniteliğiyle işaretlemeyi gerektirmeyen ek bir uygulama . Tek dezavantajı, bir nesnenin anonim sınıfa sarılması gerektiğidir, bu nedenle ikili serileştirme ile elde edilen bayt dizisi bundan farklı olabilir.

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

Anonim sınıf kullanılır çünkü BSON bir sınıf veya diziyle başlaması gerektiğinden. Bayt [] 'ı nesneye geri döndürmeyi denemedim ve çalışıp çalışmadığından emin değilim, ancak bayta [] dönüştürme hızını test ettim ve ihtiyaçlarımı tamamen karşılıyor.


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.