.NET Protobuf包裝器庫

Kation發表於2021-11-17

Wodsoft Protobuf Wrapper

內容

關於

這是一個可以幫助你不需要.proto檔案就能夠使用Protobuf序列化的一個庫。

通常.proto檔案會建立繼承IMessage介面的模型,Protobuf使用這些模型來進行序列化。

有時候我們已經在自己的.NET專案裡建立了一些模型,但我們需要使用Protobuf對這些模型進行序列化。
這時候這個庫就能幫助你使用Protobuf對已存在的模型進行序列化。

Github地址:Wodsoft.Protobuf.Wrapper

需求

Wodsoft.Protobuf.Wrapper需要NETStandard 2.0或以上。

這個庫需要工作在允許動態程式碼編譯的平臺。所以IOS不支援

安裝

在NuGet上獲取Wodsoft.Protobuf.Wrapper.

dotnet add package Wodsoft.Protobuf.Wrapper

用法

序列化

可以使用Wodsoft.Protobuf.Message類中的靜態方法Serialize
你需要一個System.IO.Stream來儲存序列化後的資料。

YourModel model = new ();
MemoryStream stream = new MemoryStream();
Message.Serialize(stream, model);

這裡也有一個過載方法。
你可以傳遞一個Google.Protobuf.CodedInputStream來替代System.IO.Stream

YourModel model = new ();
CodedInputStream input = ...;
Message.Serialize(input, model);

或者你想直接拿到序列化後的位元組陣列。

YourModel model = new ();
var bytes = Message.SerializeToBytes(model);

反序列化

你可以使用Wodsoft.Protobuf.Message類中的靜態方法Deserialize
你需要傳遞包含需要反序列化資料的System.IO.Stream
它將返回你的泛型物件T

Stream stream = ...;
YourType model = Message.Deserialize<YourType>(stream);

這裡也有一個過載方法。
你可以傳遞一個Google.Protobuf.CodedOutputStream來替代System.IO.Stream

CodedOutputStream output = ...;
YourType model = Message.Deserialize<YourType>(output);

或者你想直接從位元組陣列進行反序列化。

YourType model = Message.DeserializeFromBytes<YourType>(bytes);

欄位定義

IMessageFieldProvider.GetFields(Type type)會返回從物件對映而來的訊息欄位。

預設實現是GeneralMessageFieldProvider.Intance類。
它只會對映可讀寫的屬性到訊息欄位。

你可以建立自己的IMessageFieldProvider去對映訊息欄位。
然後通過設定靜態屬性Message<T>.FieldProvider為自定義的IMessageFieldProvider

你需要為每個需要自定義訊息欄位的型別設定IMessageFieldProvider

欄位排序

給屬性新增System.Runtime.Serialization.DataMemberAttribute特性然後設定Order屬性。
不然將根據屬性名稱進行排序。

⚠️ 如果有任何一個屬性使用了DataMemberAttribute特性,將只會序列化擁有DataMemberAttribute特性的屬性。

⚠️ 如果全部沒有使用DataMemberAttribute特性,服務如果因為部署問題使用了不同版本的模型,反序列化時可能因為欄位排序問題存在錯誤。

非空建構函式

通過呼叫靜態方法MessageBuilder.SetTypeInitializer<T>(Func<T> initializer)來設定物件初始化委託。

獲取Protobuf包裝器

我們可以直接轉換模型物件為Message<>

SimplyModel model;
Message<SimplyModel> message = model;

然後這個message可以直接被Protobuf序列化。

高階

支援的屬性型別與Protobuf型別的關係

C#型別 Protobuf型別 訊息結構
bool(?) bool Varint
sbyte(?) int32 Varint
byte(?) int32 Varint
short(?) int32 Varint
ushort(?) int32 Varint
int(?) int32 Varint
long(?) int64 Varint
uint(?) uint32 Varint
ulong(?) uint64 Varint
float(?) float Varint
double(?) double Varint
string string Length-delimited
byte[] ByteString Length-delimited
Guid(?) ByteString Length-delimited
DateTime(?) google.protobuf.Timestamp Length-delimited
DateTimeOffset(?) google.protobuf.Timestamp Length-delimited
TimeSpan(?) google.protobuf.Duration Length-delimited
IMessage Length-delimited
T[] RepeatedField<T> Length-delimited
ICollection<T> RepeatedField<T> Length-delimited
Collection<T> RepeatedField<T> Length-delimited
IList<T> RepeatedField<T> Length-delimited
List<T> RepeatedField<T> Length-delimited
IDictionary<TKey, TValue> MapField<TKey, TValue> Length-delimited
Dictionary<TKey, TValue> MapField<TKey, TValue> Length-delimited
  • (?) 意思是可以為Nullable<>可空型別。
  • 可以直接使用繼承了Google.Protobuf.IMessage的Protobuf物件作為屬性型別。
  • 所有RepeatedFieldMapField物件不能包含null值。
  • 支援bytesbyteshortushort作為屬性型別。
    它們將作為int型別進行序列化。
    如果從其它第三方來源資料進行反序列化,int可能會丟失資料。

如何工作

首先,Protobuf通過Google.Protobuf.IMessageGoogle.Protobuf.IBufferMessage介面進行序列化工作。

我們定義了一個抽象類Wodsoft.Protobuf.Message
然後定義抽象保護方法ReadWriteCalculateSize
顯式實現這些介面並呼叫這些方法。

然後定義泛型抽象類Wodsoft.Protobuf.Message<T>
這裡有一個屬性可以直接獲取到原始型別值。然後我們實現了一些隱式轉換操作。

public T Source { get; }

最後,為需要序列化的型別動態建立繼承了Message<T>的類。
通過Emit動態建立程式碼實現ReadWriteCalculateSize方法。

效能

  • 建議使用 RepeatedField<>IList<>ICollection<>作為集合屬性的型別。
    使用RepeatedField<>會獲得最佳效能(因為不需要額外型別轉換)。
  • 使用IList<>ICollection<>在序列化時會轉換為RepeatedField<>
  • 使用List<>Collection<>在序列化時會轉換為RepeatedField<>
    並且在反序列化時會轉換回List<>Collection<>(上一個會直接返回RepeatedField<>)。
  • 推薦使用 MapField<,>IDictionary<,>作為字典屬性的型別。
    使用MapField<,>會獲得最佳效能
  • 使用IDictionary<,>在序列化時會轉換為MapField<,>
  • 使用Dictionary<,>在序列化時會轉換為MapField<,>
    並且在反序列化時會轉換回Dictionary<,>(上一個會直接返回MapField<,>)。

許可證

庫使用MIT許可證。

相關文章