簡體   English   中英

如何將 object 轉換為 C# 中的字節數組

[英]How to convert an object to a byte array in C#

我有一組需要寫入二進制文件的對象。

我需要文件中的字節緊湊,所以我不能使用BinaryFormatter BinaryFormatter為反序列化需求拋出各種信息。

如果我嘗試

byte[] myBytes = (byte[]) myObject 

我得到一個運行時異常。

我需要它很快,所以我不想復制 arrays 個字節。 我只是想讓演員byte[] myBytes = (byte[]) myObject工作!

好吧,為了清楚起見,我不能在 output 文件中包含任何元數據。 只有 object 字節。 打包對象到對象。 根據收到的答復,看起來我將編寫低級Buffer.BlockCopy代碼。 也許使用不安全的代碼。

要將對象轉換為字節數組:

// 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]屬性即可啟用序列化

如果希望序列化數據非常緊湊,可以自己編寫序列化方法。 這樣,您將獲得最少的開銷。

例子:

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

}

好吧,從myObjectbyte[] myObject轉換永遠不會起作用,除非您進行了顯式轉換或者myObjectbyte[] 你需要某種類型的序列化框架。 那里有很多,包括協議緩沖區,它對我來說是近在咫尺的。 就空間和時間而言,它非常“精益求精”。

你會發現幾乎所有的序列化框架對你可以序列化的內容都有很大的限制,但是 - 由於是跨平台的,Protocol Buffers 比某些框架更多。

如果您可以提出更多要求,我們可以為您提供更多幫助 - 但它永遠不會像鑄造一樣簡單......

編輯:只是為了回應這個:

我需要我的二進制文件來包含對象的字節。 只有字節,沒有任何元數據。 打包的對象到對象。 所以我將實現自定義序列化。

請記住,對象中的字節經常是引用……因此您需要弄清楚如何處理它們。

我懷疑您會發現設計和實現您自己的自定義序列化框架比您想象的要困難。

我個人建議,如果您只需要對少數特定類型執行此操作,則不必費心嘗試提出通用序列化框架。 只需在您需要的所有類型中實現一個實例方法和一個靜態方法:

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

要記住的一件事:如果涉及繼承,一切都會變得更加棘手。 如果沒有繼承,如果您知道從什么類型開始,就不需要包含任何類型信息。 當然,還有版本控制的問題——您是否需要擔心與您的類型的不同版本的向后和向前兼容性?

我接受了 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;
    }
}

你真的在談論序列化,它可以有多種形式。 由於您想要小型和二進制,協議緩沖區可能是一個可行的選擇 - 提供版本容錯性和可移植性。 BinaryFormatter不同,protocol buffers 線格式不包括所有類型元數據; 只是非常簡潔的標記來識別數據。

在 .NET 中有一些實現; 特別是

我謙虛地認為 protobuf-net(我寫的)允許更多 .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);

這對我有用:

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

foo是一個對象,我 100% 確定它是一個字節數組。

看看Serialization ,這是一種將整個對象“轉換”為字節流的技術。 您可以將其發送到網絡或將其寫入文件,然后稍后將其恢復回對象。

要直接訪問對象的內存(進行“核心轉儲”),您需要進入不安全的代碼。

如果您想要比 BinaryWriter 或原始內存轉儲更緊湊的東西,那么您需要編寫一些自定義序列化代碼,從對象中提取關鍵信息並以最佳方式對其進行打包。

編輯PS 將 BinaryWriter 方法包​​裝到 DeflateStream 中以壓縮數據非常容易,這通常會使數據大小大致減半。

我相信你想要做的事情是不可能的。

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描述了對象的布局/類型,但每次運行程序時它的值都會改變。

編輯:這是愚蠢的不要這樣做-->如果您已經知道要反序列化的對象的類型,您可以將這些字節切換為BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value) at反序列化的時間。

使用二進制格式化程序現在被認為是不安全的。 見 --> 微軟文檔

只需使用 System.Text.Json:

序列化為字節:

JsonSerializer.SerializeToUtf8Bytes(obj);

要反序列化為您的類型:

JsonSerializer.Deserialize(byteArray);

如果您只有文字或類似商店的東西,您可以執行以下操作:

byte[] byteArray = Encoding.ASCII.GetBytes(myObject.text);

否則,您將不得不以更復雜的方式序列化。

我找到了另一種將對象轉換為字節 [] 的方法,這是我的解決方案:

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

問候

我發現最好的方法這種方法對我有用使用Newtonsoft.Json

public TData ByteToObj<TData>(byte[] arr){
                    return JsonConvert.DeserializeObject<TData>(Encoding.UTF8.GetString(arr));
    }

public byte[] ObjToByte<TData>(TData data){
            var json = JsonConvert.SerializeObject(data);
            return Encoding.UTF8.GetBytes(json);
}

此方法從對象返回一個字節數組。

private byte[] ConvertBody(object model)
        {
            return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model));
        }

跨度對於這樣的事情非常有用。 簡而言之,它們是非常快的 ref 結構,具有指向第一個元素的指針和長度。 它們保證了 memory 的連續區域,並且 JIT 編譯器能夠基於這些保證進行優化。 它們就像指針 arrays 一樣工作,您可以在 C 和 C++ 語言中隨時看到。

自從添加了 span 之后,您就可以使用兩個MemoryMarshal函數來獲取 object 的所有字節,而無需流的開銷。 在引擎蓋下,它只是一點點鑄造。 就像你問的那樣,除非你將它們復制到一個數組或另一個跨度,否則沒有額外的分配到字節。 這是用於獲取一個字節的兩個函數的示例:

public static Span<byte> GetBytes<T>(ref T o)
    where T : struct
{
    if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
        throw new Exception($"Type {nameof(T)} is or contains a reference");

    var singletonSpan = MemoryMarshal.CreateSpan(ref o, 1);
    var bytes = MemoryMarshal.AsBytes(singletonSpan);
    return bytes;
}

第一個 function MemoryMarshal.CreateSpan引用了一個 object,其長度表示有多少相同類型的相鄰對象緊隨其后。 它們必須相鄰,因為跨度保證 memory 的連續區域。在這種情況下,長度為 1,因為我們只使用單個 object。在引擎蓋下,它是通過創建從第一個元素開始的跨度來完成的。

第二個 function MemoryMarshal.AsBytes獲取一個跨度並將其轉換為一個字節跨度。 這個跨度仍然涵蓋參數 object,因此對字節的任何更改都將反映在 object 中。幸運的是,跨度有一個名為ToArray的方法,它將跨度中的所有內容復制到一個新數組中。 在引擎蓋下,它在字節而不是 T 上創建跨度,並相應地調整長度。 如果您想要復制到一個跨度,則可以使用CopyTo方法。

if 語句用於確保您不會出於安全原因復制屬於或包含引用的類型的字節。 如果它不存在,您可能正在復制對不存在的 object 的引用。

類型 T 必須是結構,因為MemoryMarshal.AsBytes需要不可為 null 的類型。

您可以使用以下方法使用 System.Text.Json 序列化將對象列表轉換為字節數組。

private static byte[] CovertToByteArray(List<object> mergedResponse)
{
var options = new JsonSerializerOptions
{
 PropertyNameCaseInsensitive = true,
};
if (mergedResponse != null && mergedResponse.Any())
{
return JsonSerializer.SerializeToUtf8Bytes(mergedResponse, options);
}

 return new byte[] { };
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM