1. 程式人生 > 實用技巧 >開源!一款功能強大的高效能二進位制序列化器Bssom.Net

開源!一款功能強大的高效能二進位制序列化器Bssom.Net

好久沒更新部落格了,我開源了一款高效能的二進位制序列化器Bssom.Net和新穎的二進位制協議Bssom,歡迎大家Star,歡迎參與專案貢獻!

Net開源技術交流群 976304396,禁止水,只能討論技術, 歡迎與我討論和效能相關的技術話題!
另外,我還在抖音申請了一個賬號,用來記錄自己的日常生活, 想了解我平常是怎麼寫程式碼的嗎? 來關注我一下,哈哈! 抖音號: 198152455
Bssom.Net專案地址: https://github.com/1996v/Bssom.Net
Bssom協議地址: https://github.com/1996v/Bssom

A small, high performance, powerful serializer using bssom binary protocol

Bssom.Net是一個使用bssom結構協議實現的高效能結構化二進位制序列化器,它具有以下特點,小巧,快速,功能性強.

  1. 小巧,檔案僅300多k
  2. 快速,它具有一流的序列化和反序列化效能
  3. 功能性強:
    • 可以獲取物件被序列化後的大小而不用完整序列化物件
    • 可以讀取物件中的某個元素而不用完整的反序列化物件
    • 可以更改物件中的某個元素而不用完整的序列化
    • 序列化後的格式具有自描述性

為什麼需要?

目前c#已經有很多二進位制序列化器, 但這些序列化器都只提供了單一的序列化和反序列化功能.

Bssom.Net採取了Bssom協議, 使序列化後的資料具有結構化特性, 且擁有直接對欄位進行編組的功能, 這使得Bssom.Net能做到其它序列化器所達不到的事情.

  • 當我想在序列化物件時知道物件被序列化後的大小, 以提前來選擇該物件應該被序列化的正確位置(如資料庫引擎的FSM演算法), 那麼Bssom.Net能夠滿足你
  • 當我擁有一個大的二進位制資料, 但是我只想無合約的讀取其中一個欄位, 以避免完整的反序列化開銷, 那麼Bssom.Net能夠滿足你
  • 當我擁有一個已經被序列化後的資料包, 我只想無合約的修改其中一個欄位, 以避免重新序列化的開銷, 那麼Bssom.Net能夠滿足你
  • 當我想讓物件被序列化後仍能保留型別資訊, 而不用依賴實體, 那麼Bssom.Net能夠滿足你

什麼是Bssom協議?

Bssom(Binary search algorithm structure model object binary marshalling)是一個使用二分查詢演算法模型對物件進行結構化編組的協議,被編組後的資料具有特殊的元資料資訊,根據這些元資料資訊可以高效的僅讀取和更改物件中的某個元素,這樣可以在對大物件進行序列化和反序列化的過程中不必因為只讀取或只寫入一個欄位而造成完整的序列化開銷。

大綱

1.效能


這裡是與.NET平臺下非常優秀的兩款序列化程式(MessagePackProtobuf-net)進行效能比較的基準.

柱狀資料代表執行相同任務所花費的時間, 越低代表性能越快, 折線資料代表執行相同任務所產生的GC, 越低代表在執行中所產生的垃圾越少 , 從效能比較結果可以看出Bssom.Net的效能是非常優異的.

Bssom.Net使用很多技術來提高效能.

  • 使用記憶體池技術, 用於寫入的記憶體可以複用
  • 使用表示式和Emit動態程式設計技術, 對型別進行了特殊處理, 且避免值型別裝箱拆箱
  • 使用泛型靜態快取, 避免了字典查詢開銷
  • 包裝了異常丟擲程式碼, 以增加內聯的可能性
  • 更多的對強型別進行呼叫, 而不是介面抽象
  • 預處理Map2型別的元資料, 在序列化時不需要對其進行再次編碼
  • 在查詢Map2鍵時, 提前固定區域性引用, 而不是標準函式呼叫
  • 解析Map1型別時, 自動構建8位元組的自動機跳躍查詢
  • 值得一提的是, 出於減少依賴, 減少體積的目的, Bssom.Net並沒有依賴System.Memory.dll, 因此無法使用Span<T>,Memory<T>等型別, 這意味著Bssom.Net的實現將無法使用ByReference<T>這一JIT內部特性, 因此目前的讀寫器將不具備讀寫區域性化和去虛擬化及內聯呼叫的這三個效能優化點 ( 但即使這樣, 目前的Bssom.Net效能依然非常優秀 ) , 若將來有可能支援Span<T>型別的話, 那麼Bssom.Net將會通過一些額外的效能技巧來再次提升效能.

2.讀寫器

Bssom.Net對於讀取和寫入的入口並不是直接使用原生的Byte[], 而是提供了緩衝區介面IBssomBuffer和寫入器介面IBssomBufferWriter.
與原生的byte[]不同, 介面將更加靈活, 實現IBssomBuffer後可以從任意來源來讀取資料, 實現IBssomBufferWriter後可以將資料寫在任意地方(比如非連續的片段)

IBssomBuffer

IBssomBuffer是一個用於序列化的緩衝區介面, 提供了讀取的行為.

方法 描述
Position 緩衝區中的當前位置
ReadRef 從當前緩衝區中的位置讀取指定大小序列的引用
Seek 設定當前緩衝區的位置
SeekWithOutVerify 設定當前緩衝區的位置, 並且不對position的邊界進行驗證
TryReadFixedRef 嘗試從當前緩衝區中的位置讀取一個可以固定的位元組序列的引用, 當進行Seek操作的時候不會影響被固定位元組的引用位置
UnFixed 用於取消由TryReadFixedRef所固定的引用, 此方法的呼叫始終和TryReadFixedRef對稱

IBssomBufferWriter

IBssomBufferWriter是基於緩衝區的寫入介面, 提供了寫入行為

方法 描述
Buffered 在緩衝區中已寫入位元組的數目
Advance 用於指示已寫入緩衝區的部分
Position 寫入器的當前位置
Seek 設定當前寫入器的位置
SeekWithOutVerify 設定當前寫入器的位置, 並且不對Buffered的邊界進行驗證
GetRef 從當前位置獲取用於寫入的位元組序列的引用
CanGetSizeRefForProvidePerformanceInTryWrite 在欄位編組中, 當前位置是否能提供指定大小的位元組序列引用以用來提供內部某些型別寫入的效能
GetBssomBuffer 獲取當前寫入器所使用的緩衝區

Bssom.Net內部已經對byte[], Stream進行了IBssomBufferIBssomBufferWriter介面的封裝, 使用者無需手動封裝

3.格式化器

格式化是Bssom.Net將.Net物件和Bssom格式進行互相轉換的一個過程. Bssom.Net通過IBssomFormatter<T>來實現對物件的格式化.

API 描述
Size 獲取物件被序列化後的大小
Serialize 將物件序列化成Bssom二進位制格式
Deserialize 將Bssom二進位制格式反序列化成物件

Bssom.Net內部已經內建了許多格式化器, 如.NET的基元型別, 鍵值對型別, 可迭代型別... 他們在Bssom.Serializer.Formatters名稱空間下, 你可以找到它並直接呼叫它.

如果你不需要特殊的處理某個型別的話, 那麼這些格式化器基本可以覆蓋你的大部分需求. 而如何找到格式化器, 這則是解析器所需要做的.

4.解析器

解析是將.Net型別物件獲取到對應的格式化器的一個過程.Bssom.Net通過IFormatterResolver來實現對物件的解析.

API 描述
GetFormatter 獲取物件的格式化器例項

解析器通常具備解析型別和儲存格式化器這兩種功能, Bssom.Net中已實現的解析器在內部會對.net型別進行格式化器的查詢, 然後通過靜態泛型的特性快取被找到的格式化器, 完成了將一個或一組.net型別繫結到對應的格式化器的這樣過程.

IFormatterResolver是Bssom.NET開始對物件序列化的最上層的入口, 他們在Bssom.Serializer.Resolvers名稱空間下.

名稱 描述
PrimitiveResolver 該解析器提供了sbyte,Int16,Int32,Int64,byte,UInt16,UInt32,UInt64,Single,Double,bool,char,Guid,Decimal,string,DateTime的型別的解析器
AttributeFormatterResolver 獲取並提供使用者自定義格式化器的例項
BuildInResolver 提供了StringBuilder,BitArray,DataTable等型別的解析器
BssomValueResolver 提供了BssomValue型別的解析器
IDictionaryResolver 獲取和生成具有IDictionary行為的型別的解析器, 該解析器抽象了BCL中對於鍵值對定義的行為規則, 為滿足該規則的物件進行動態解析程式碼的生成.在解析器內部, 將通過執行時的配置選項來選擇Map1Map2的兩種格式
ICollectionResolver 獲取和生成具有IColloction行為的型別的解析器, 該解析器抽象了BCL中對於收集器定義的行為規則, 為滿足該規則的物件進行動態解析程式碼的生成. 在解析器內部, 如果集合中的元素型別為基元型別, 則將其解析成Array1格式, 否則解析為Array2格式
MapCodeGenResolver 獲取和生成物件的公開欄位和屬性進行BssomMap型別編碼的解析器, 若物件為介面, 則會自動生成該介面的實現作為反序列化的載體.在解析器內部, 始終將型別解析為Map2格式, 且提供Map1Map2兩種格式的反序列化程式碼
ObjectResolver 提供了Object型別的解析器
CompositedResolver 複合解析器,組合了Object,Primitive,Attribute,BssomValue,BuildIn,IDictionary,ICollection,MapCodeGen解析器

因為IDictionaryResolverICollectionResolver中定義的足夠抽象的規則,Bssom.Net不需要為未來.NET可能出現的新的IDictionaryIColloction實現而編寫特定的解析程式碼.

在Bssom.Net中可以通過BssomSerializerOptions中的FormatterResolver屬性來注入序列化所需要的解析器, 預設為CompositedResolver, CompositedResolver將會對型別依次從 Object,Primitive,Attribute,BssomValue,BuildIn,IDictionary,ICollection,MapCodeGen解析器中進行查詢, 直到找到對應的解析器.

5.擴充套件

讓我們看一下Bssom.Net序列化的過程:

 input T -> Call serialize(T) -> Find BssomResolver -> Provide type formatter -> formatter.Serialize(T);

在整個序列化的過程中, 每個步驟都是透明的, 這意味著若使用者對Bssom.Net內部定義的解析器或格式化器不滿意的話, 則可以自己擴充套件它.

使用者可以自己通過實現IFormatterResolverIBssomFormatter替代預設的解析器, 在Bssom.Serializer.Binary.BssomBinaryPrimitives(在即將到來的小版本中將重構該類)和讀寫器本身所暴露的公開API中提供對Bssom格式的低階寫入和讀取實現.

簡單示例可以參考更多可能介紹

6.高階API

BssomSerializer

BssomSerializer是Bssom最上層的API, 在Bssom.Serializer名稱空間下, 是Bssom開始工作的入口. 它的靜態方法構成了Bssom.Net的主要API.

API 描述 過載
Size 在不進行序列化的情況下, 獲取物件被序列化後的二進位制資料大小 (t, option),(ref context, t)
Serialize 將給定的值序列化為Bssom二進位制 (byte[], t, option), (stream, t, option), (IBssomBufWriter, t, option), (ref context, t)
Deserialize 將Bssom二進位制資料反序列化成.net物件 (byte[], option),(stream, option),(IBssomBuf, option),(ref context)
SerializeAsync 非同步的序列化給定的值為Bssom二進位制 同上
DeserializeAsync 非同步的將Bssom二進位制資料反序列化成.net物件 同上

BssomSerializerOptions

BssomSerializer作為最上層的API,我們在呼叫它時,需要傳遞一個可空的BssomSerializerOptions型別的Option引數.
BssomSerializerOptions是Bssom在整個序列化工作期間所需要使用的配置. 預設為BssomSerializerOptions.Default.

  • FormatterResolver : 在Option中,你可以為FormatterResolver註冊解析器, 如果沒有手動註冊, 則使用預設的CompositedResolver, Bssom將總是通過FormatterResolver來對型別進行解析.
  • Security : 這是用於序列化期間的安全相關選項, 目前僅提供了在反序列化期間對深度的驗證,預設為 不限制
  • IsPriorityToDeserializeObjectAsBssomValue : 該選項決定了反序列化時是否將Object型別轉換為BssomValue型別, 如果為false, 則預設反序列化為原生型別. 預設為false.
  • IsUseStandardDateTime : Bssom.Net對DateTime型別實現了標準的Bssom協議Unix格式.NET平臺的本地格式, 本地格式具有更少的位元組, 但不具備和其它平臺的互動性, 預設為false.
  • IDictionaryIsSerializeMap1Type : 此選項決定了對具有IDictionary行為的型別預設使用哪種格式進行序列化, 如果為true則使用Map1格式, 否則為Map2格式. 預設為true

BssomSerializeContext

BssomSerializeContext提供了序列化期間所使用的上下文資訊, 這其中也包括了BssomSerializerOptions

  • BssomSerializerOptions : 序列化期間所使用的配置資訊
  • ContextDataSlots : 提供了一個數據槽, 供使用者在序列化期間自己儲存和讀取的一個儲存介質
  • CancellationToken : 一個序列化操作取消的標記, 使用者可以中途取消正在進行的序列化操作

7.欄位編組

Bssom.Net擁有讀取欄位而不用完全反序列化和更改值而不用完全序列化功能, 這是因為Bssom協議有著良好的結構化特徵, 在Bssom.Net的實現裡, 這樣的功能則暴露在BssomFieldMarshaller中.

BssomFieldMarshaller

BssomFieldMarshaller提供一套API用於對被序列化後的資料進行更低粒度的控制.

API 描述
IndexOf 通過特殊的輸入格式來獲取被指定的物件在Bssom二進位制中的位置,返回偏移量資訊
ReadValue 通過指定的偏移量資訊來讀取整個元素
ReadValueType 通過指定的偏移量資訊僅讀取元素型別
ReadValueTypeCode 通過指定的偏移量資訊僅讀取元素型別的二進位制碼
ReadValueSize 通過指定的偏移量資訊來獲取元素在Bssom二進位制中所儲存的大小
ReadArrayCountByMapType 通過指定的偏移量資訊來讀取BssomArray的元素數量
ReadAllKeysByMapType 通過指定的偏移量資訊來讀取BssomMap中的元資料(包含Key和值的偏移量)
TryWrite 通過指定的偏移量資訊在Bssom二進位制中重新對值進行寫入, 若寫入值的寬度大於被寫入槽的寬度,則失敗

每種方法都提供了 byte[]IBssomBuf 的過載

簡單欄位訪問語言

Bssom.Net為IndexOf定義了一種簡單的欄位訪問語言, 該語言共定義了兩種訪問形式, 一種是訪問Map型別(該Map型別的鍵必須為String型別), 一種是訪問Array型別. 兩種訪問形式可以自由組合.

  • [Key] : 代表通過Key來訪問Map型別的值, 輸入的Key只表示String型別
  • $Index : 代表通過下標來訪問Array型別的元素, 輸入的Index只能是整數型別

假設有如下資料

{
   "Postcodes" : {   
		  "WuHan" : [430070,430071,430072,430073],
		  "XiangYang" : [441000,441001,441002]
		},
   "Province" : "HuBei"
}

可以通過如下方式進行元素訪問, 在示例中可以瞭解更多細節

[Postcodes][WuHan]$1  => 4330071
[Province]  => "HuBei"

自定義欄位訪問形式介面

Bssom.Net為IndexOf提供了IIndexOfInputSource介面用來接收自定義的欄位訪問源, 使用該介面後Map型別的Key將不再受限制, Key可以為任意輸入型別.

IndexOfObjectsInputSource 是 Bssom.Net為使用者提供的IIndexOfInputSource介面的通用實現. 它接收一組可迭代的物件,當呼叫IndexOf的時候, 將依次對物件進行迭代.

假設有如下資料

{
   2018-01-01 : {
         0 : ["Rain1","Rain2","Rain3"],
         4 : ["Rain4","Fair5","Fair6"]   
    }
}

可以通過如下方式進行元素訪問, 在示例中可以瞭解更多細節

new IndexOfObjectsInputSource(new Entry[]{ 
     new Entry(DateTime.Parse("2018-01-01"),ValueIsMapKey: true),
     new Entry(3,ValueIsMapKey: true),
     new Entry(1,ValueIsMapKey: false),
  })

output => "Fair5"

8.動態程式碼生成

Bssom.Net對IDictionaryResolver, ICollectionResolver, MapCodeGenResolver, ObjectResolver 使用了動態程式碼生成技術, 通過表示式樹和Emit共同生成執行時程式碼, 如果應用程式是純AOT環境, 則將不支援.

MapCodeGenResolver中對Map1型別的反序列化使用了以8位元組(64位字長)為單位的類字首樹的自動機查詢模式, 這是非常有效且快速的方式, 它避免了對字串進行完全Hash運算以及字元比較開銷, 通過對MapCodeGenResolver.Save()方法你將看到這些自動生成的程式碼.

MapCodeGenResolver中對Map2型別的反序列化則使用了內建的Bssom協議的Map格式查詢程式碼,該程式碼是狀態機模式編寫, 分為快速和低速版, 這取決於讀取器是否能夠提供 TryReadFixedRef.

另外,對於Size方法,MapCodeGenResolver的處理也是非常快速的,因為它已經提前計算好了元資料的大小,並且內聯了基元欄位本身的固定大小.

9.特性

Bssom.Net中目前擁有5個特性.

  • AliasAttribute : 別名特性, 用於修改Map格式物件欄位在二進位制中所儲存的欄位名稱
  • BssomFormatterAttribute : 自定義格式化特性, 當欄位屬性或型別被該特性標記後, 此型別的格式化將採用該特性所指定的格式化器
  • IgnoreKeyAttribute : 忽略某一個Key, 序列化時將忽略被標記的欄位, 適用於Map格式
  • OnlyIncludeAttribute : 僅包含某一個Key, 序列化時僅包含該Key, 適用於Map格式, 與IgnoreKeyAttribute作用相反,優先順序更高
  • SerializationConstructorAttribute : 為型別的反序列化指定一個建構函式

10.更多的可能性

你可以自己編寫解析器, 編寫格式化器, 也可以定義你自己的特性, 也可以封裝用於序列化的Option, 並且Bssom.Net還提供了上下文資料槽的支援, 這可以讓序列化行為變得多樣性.

如果你能為Bssom.Net提供有用或者側重於高效能的擴充套件包, 那麼請您告訴我.

下面示例編寫了以String型別為原型的解析器, 該解析器通過與上下文互動的方式來帶來字串型別序列化效能的提升.

public sealed class MyStringFormatterResolver : IFormatterResolver
{
    public static MyStringFormatterResolver Instance = new MyStringFormatterResolver();

    public IBssomFormatter<T> GetFormatter<T>()
    {
        return FormatterCache<T>.Formatter;
    }

    private static class FormatterCache<T>
    {
        public static readonly IBssomFormatter<T> Formatter;

        static FormatterCache()
        {
            if (typeof(T) == typeof(string))
                Formatter = (IBssomFormatter<T>)(object)MyStringFormatter.Instance;
        }
    }
}

public sealed class MyStringFormatter : IBssomFormatter<string>
{
    public static MyStringFormatter Instance = new MyStringFormatter();

    public string Deserialize(ref BssomReader reader, ref BssomDeserializeContext context)
    {
        if (reader.TryReadNull())
        {
            return null;
        }

        reader.EnsureType(BssomType.StringCode);
        int dataLen = reader.ReadVariableNumber();
        ref byte refb = ref reader.BssomBuffer.ReadRef((int)dataLen);
        fixed (byte* pRefb = &refb)
        {
            return new string((sbyte*)pRefb, 0, (int)dataLen, UTF8Encoding.UTF8);
        }
    }

    public void Serialize(ref BssomWriter writer, ref BssomSerializeContext context, string value)
    {
        if (value == null)
        {
            writer.WriteNull();
            return;
        }

        int valueUtf8Size = context.ContextDataSlots.PopMyStringSize();

        writer.WriteBuildInType(BssomType.StringCode);
        writer.WriteVariableNumber(valueUtf8Size);

        ref byte refb = ref writer.BufferWriter.GetRef(valueUtf8Size);
        fixed (char* pValue = value)
        fixed (byte* pRefb = &refb)
        {
            UTF8Encoding.UTF8.GetBytes(pValue, value.Length, pRefb, valueUtf8Size);
        }
        writer.BufferWriter.Advance(valueUtf8Size);
    }

    public int Size(ref BssomSizeContext context, string value)
    {
        if (value == null)
            return BssomBinaryPrimitives.NullSize;

        int dataSize = UTF8Encoding.UTF8.GetByteCount(value);
        context.ContextDataSlots.PushMyStringSize(dataSize);
        return BssomBinaryPrimitives.BuildInTypeCodeSize + dataSize;
    }
}
public void MyTest()
{
     var option = BssomSerializerOptions.Default.WithFormatterResolver(MyStringFormatterResolver.Instance);
     string str = RandomHelper.RandomValue<string>();
     BssomSizeContext sizeContext = new BssomSizeContext(option);
     int len = BssomSerializer.Size(ref sizeContext, str);
     if (len > 1000)
         throw new Exception("Size of value storage binary exceeded");

     BssomSerializeContext serContext = new BssomSerializeContext(option);
     sizeContext.ContextDataSlots.SetMyStringStack(serContext.ContextDataSlots);
     var bytes = BssomSerializer.Serialize(ref serContext, str);
     var deStr = BssomSerializer.Deserialize<string>(bytes);

     Assert.Equal(str,deStr);
}

上面的程式碼是單獨為String定義了一個新的解析器和新的格式化器, 該格式化器可以將Size方法中對字串計算的UTF8大小儲存在上下文中, 這樣在序列化時不用重複對String再做一次UTF8大小計算.

11.如何使用

Bssom.Net是無合約的, 開箱即用, 這裡有些示例程式碼.

Size

BssomSerializer.Size 方法用於 獲取物件被序列化後的二進位制資料大小,高效能的內部實現,幾乎無開銷

//獲取值被序列化後的大小
object value = RandomHelper.RandomValue<object>();
int size = BssomSerializer.Size(value, option: BssomSerializerOptions.Default);
//使用上下文獲取值被序列化後的大小
BssomSizeContext context = new BssomSizeContext(BssomSerializerOptions.Default);
object value = RandomHelper.RandomValue<object>();
int size = BssomSerializer.Size(ref context, value);

Serialize

BssomSerializer.Serialize 方法用於 將給定的值序列化為Bssom二進位制,高效能的內部實現,以下是部分常用方法,每個方法都擁有CancellationToken的過載

//直接對物件進行序列化,將返回一個被序列化後的位元組陣列
object value = RandomHelper.RandomValue<object>();
byte[] binary = BssomSerializer.Serialize(value, option: BssomSerializerOptions.Default);
//將物件序列化到指定的位元組陣列中,若容量不夠將自動擴容,最終返回序列化的位元組數
object value = RandomHelper.RandomValue<object>();
byte[] buf = local();
int serializeSize = BssomSerializer.Serialize(ref buf, 0, value, option: BssomSerializerOptions.Default);
//將物件序列化到自定義的寫入器中
object value = RandomHelper.RandomValue<object>();
IBssomBufferWriter writer = new Impl();
BssomSerializer.Serialize(value, writer, option: BssomSerializerOptions.Default);
//使用序列化上下文進行序列化
object value = RandomHelper.RandomValue<object>();
BssomSerializeContext context = new BssomSerializeContext(BssomSerializerOptions.Default);
byte[] binary = BssomSerializer.Serialize(ref context, value);
//將物件序列化到流中
object value = RandomHelper.RandomValue<object>();
Stream stream = new MemoryStream();
BssomSerializer.Serialize(stream, value, option: BssomSerializerOptions.Default);
//非同步的將物件序列化到流中
object value = RandomHelper.RandomValue<object>();
Stream stream = new MemoryStream();
await BssomSerializer.SerializeAsync(stream, value, option: BssomSerializerOptions.Default);

Deserialize

BssomSerializer.Deserialize 方法用於 將給定的Bssom緩衝區反序列化為物件,高效能的內部實現,以下是部分常用方法,每個方法都擁有CancellationToken的過載

//從給定的位元組陣列中反序列化物件
byte[] buf = remote();
T value = BssomSerializer.Deserialize<T>(buf, 0, out int readSize, option: BssomSerializerOptions.Default);
//從給定的buffer中反序列化物件
IBssomBuffer buffer = remote();
object value = BssomSerializer.Deserialize<object>(buffer, option: BssomSerializerOptions.Default);
//使用上下文從給定的buffer中反序列化物件
BssomDeserializeContext context = new BssomDeserializeContext(BssomSerializerOptions.Default);
IBssomBuffer buffer = remote();
object value = BssomSerializer.Deserialize<object>(ref context, buffer);
//從流中反序列化物件
Stream stream = remote();
object value = BssomSerializer.Deserialize<object>(stream, option: BssomSerializerOptions.Default);
//非同步的從流中反序列化物件
Stream stream = remote();
object value = await BssomSerializer.DeserializeAsync<object>(stream, option: BssomSerializerOptions.Default);
//傳遞一個Type, 從流中反序列化物件為指定的Type型別
Stream stream = remote();
Type type = typeof(class);
object value = BssomSerializer.Deserialize(stream, type, option: BssomSerializerOptions.Default);
//傳遞一個Type, 非同步的從流中反序列化物件為指定的Type型別
Stream stream = remote();
Type type = typeof(class);
object value = await BssomSerializer.DeserializeAsync(stream, type, option: BssomSerializerOptions.Default);

ReadValue

BssomFieldMarshaller.ReadValue 方法用於 在二進位制資料中僅讀取某一個值,如果你只想讀取物件中的某一個值,而不用完整的反序列化它,那麼這個方法非常有用

//通過內嵌的簡單欄位訪問語言,獲取Dict中的一個Key對應的值
var val = new Dictionary<string, object>() {
            { "A",(int)3},
            { "B",(DateTime)DateTime.MaxValue},
        };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("[A]")
bsfm.ReadValue<int>(fieldOffInfo).Is(3);
//通過內嵌的簡單欄位訪問語言,獲取class中的一個屬性的值
var val = new MyClass() {
            Name = "bssom",
            Nature = "Binary"
        };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("[Name]")
bsfm.ReadValue<string>(fieldOffInfo).Is("bssom");
//通過內嵌的簡單欄位訪問語言,獲取陣列中的一個屬性的值
var val = new object[] { (int)1,(double)2.2 }
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("$1")
bsfm.ReadValue<double>(fieldOffInfo).Is((double)2.2);
//通過內嵌的簡單欄位訪問語言,組合獲取一個物件
var val = new MyClass() {
            Name = "bssom",
            Nature = "Binary",
            Data = new int[] { 3, 2, 1} 
        };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf("[Data]$1")
bsfm.ReadValue<int>(fieldOffInfo).Is(2);
//通過自定義的欄位訪問形式,組合獲取一個物件
var val = new Dictionary<object, object>() {
            { DateTime.Parse("2018-01-01"), new object[]{'A','B'} },
            { "Charec",(DateTime)DateTime.MaxValue},
        };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
IIndexOfInputSource input = new IndexOfObjectsInputSource(new Entry[]{ 
     new Entry(DateTime.Parse("2018-01-01"),ValueIsMapKey: true),
     new Entry(1,ValueIsMapKey: false),
  })
BssomFieldOffsetInfo fieldOffInfo = bsfm.IndexOf(input)
bsfm.ReadValue<int>(fieldOffInfo).Is('B');

ReadAllMapKeys

BssomFieldMarshaller.ReadAllMapKeys 方法用於 在二進位制資料中讀取Map格式的所有Key和值偏移量,如果你想了解該二進位制資料中的鍵值情況,但又不想完全讀取它,那麼這個方法非常有用.

var val = new Dictionary<object, object>(){
           { "Id" , 1 },
           { "Path" , "../t.jpg" },
           { "Data" , new byte[3000] }
};
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
bsfm.ReadAllMapKeys<object>(BssomFieldOffsetInfo.Zero).Print();
//output
//  line 1: BssomString::"Id", BssomFieldOffsetInfo
//  line 2: BssomString::"Path", BssomFieldOffsetInfo
//  line 3: BssomString::"Data", BssomFieldOffsetInfo

TryWriteValue

BssomFieldMarshaller.TryWriteValue 方法用於 對二進位制資料的值進行修改,當你只想修改物件中的某個值,而不用重新序列化整個物件時,那麼這個方法非常有用

//修改字串物件
var val = "abcd";
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);
bsfm.TryWrite(BssomFieldOffsetInfo.Zero, "abc");
string upVal = BssomSerializer.Deserialize<string>(buf);
upVal.Is("abc");
//修改IDict物件中的某個鍵
var val = new Dictionary<string, object>(){
           { "Id" , 1 },
           { "Path" , "../t.jpg" },
           { "Data" , new byte[3000] }
};
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);

bsfm.TryWrite(bsfm.IndexOf("[Id]"), 3);
var upVal = BssomSerializer.Deserialize<Dictionary<string, object>>(buf);
upVal["Id"].Is(3);
//修改IDict物件中的某個鍵
var val = new MyClass() {
            Name = "bssom",
            Nature = "Binary",
            Data = new int[] { 3, 2, 1} 
        };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);

bsfm.TryWrite(bsfm.IndexOf("[Name]"), "zz");
var upVal = BssomSerializer.Deserialize<MyClass>(buf);
upVal["Name"].Is("zz");
//修改Array物件中的某個元素
var val = new object[] { "abc" , 37 };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);

bsfm.TryWrite(bsfm.IndexOf("$1"), 40);
var upVal = BssomSerializer.Deserialize<MyClass>(buf);
((int)upVal[1]).Is(40);
//組合修改物件中的某個元素
var val = new object[] { 
        22, 
        37, 
        new MyClass() {
            Name = "bssom",
            Nature = "Binary",
            Data = new int[] { 3, 2, 1} 
        }
 };
var buf = BssomSerializer.Serialize(val);
var bsfm = new BssomFieldMarshaller(buf);

bsfm.TryWrite(bsfm.IndexOf("$2[Name]"), "zz");
var upVal = BssomSerializer.Deserialize<MyClass>(buf);
((MyClass)upVal[1]).Name.Is("zz");

如何使用特性

如何定義擴充套件

12.如何參與專案貢獻

如果你想參與本專案的發展,那麼我將非常榮幸和高興,歡迎Fork或Pull Request,也可以加入QQ群976304396來進行開源技術的探討

點選加入群聊.NET開源技術交流群 禁水,只能聊技術

13.誰在使用

  • BssomDB(即將開源) 一個使用Bssom協議的純C#的嵌入式事務型文件資料庫

14.其它

我喜歡和我一樣的人交朋友,不被環境影響,自己是自己的老師,歡迎加群 Net開源技術交流群 976304396 ,與我討論與效能相關的話題!
想了解我日常是怎樣寫程式碼的嗎? 歡迎關注我的抖音賬號: 198152455 .

作者:小曾
出處:https://www.cnblogs.com/1996V/p/13884968.html 歡迎轉載,但請保留以上完整文章,在顯要地方顯示署名以及原文連結。
Net開源技術交流群 976304396 , 抖音賬號: 198152455