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物件作為屬性型別。 - 所有
RepeatedField
與MapField
物件不能包含null
值。 - 支援
byte
,sbyte
,short
和ushort
作為屬性型別。
它們將作為int
型別進行序列化。
如果從其它第三方來源資料進行反序列化,int
可能會丟失資料。
如何工作
首先,Protobuf通過Google.Protobuf.IMessage
與Google.Protobuf.IBufferMessage
介面進行序列化工作。
我們定義了一個抽象類Wodsoft.Protobuf.Message
。
然後定義抽象保護方法Read
,Write
,CalculateSize
。
顯式實現這些介面並呼叫這些方法。
然後定義泛型抽象類Wodsoft.Protobuf.Message<T>
。
這裡有一個屬性可以直接獲取到原始型別值。然後我們實現了一些隱式轉換操作。
public T Source { get; }
最後,為需要序列化的型別動態建立繼承了Message<T>
的類。
通過Emit動態建立程式碼實現Read
,Write
,CalculateSize
方法。
效能
- 建議使用
RepeatedField<>
,IList<>
或ICollection<>
作為集合屬性的型別。
使用RepeatedField<>
會獲得最佳效能(因為不需要額外型別轉換)。 - 使用
IList<>
或ICollection<>
在序列化時會轉換為RepeatedField<>
。 - 使用
List<>
或Collection<>
在序列化時會轉換為RepeatedField<>
。
並且在反序列化時會轉換回List<>
或Collection<>
(上一個會直接返回RepeatedField<>
)。 - 推薦使用
MapField<,>
或IDictionary<,>
作為字典屬性的型別。
使用MapField<,>
會獲得最佳效能。 - 使用
IDictionary<,>
在序列化時會轉換為MapField<,>
。 - 使用
Dictionary<,>
在序列化時會轉換為MapField<,>
。
並且在反序列化時會轉換回Dictionary<,>
(上一個會直接返回MapField<,>
)。
許可證
庫使用MIT許可證。