.NET Protobuf包裝器庫

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許可證。

Tags: