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