我有一個想法,有一個能夠進行跨平臺的高性能資料協議規范,能夠讓資料在兩個不同的程式之間進行讀取,最好能夠支持直接將object序列化,那就完美了,
目標
- 支持任意Object序列化
- 支持從類似System.String的字串中獲取類的資訊并進行反序列化
- 支持簡單物件的直接序列化與反序列化
方案
Xml序列化
說到序列化,.NET自帶的XML序列化就很好用了,無奈有很多型別不支持,典型的比如Dictionary<>,而且這個東西雖然強大,但是xml的標簽機制導致多余的內容比較多,空間占用會比較大,
Binary序列化
支持任意object序列化,.NET還提供了BinaryFormatter,
// code from https://stackoverflow.com/questions/7442164/c-sharp-and-net-how-to-serialize-a-structure-into-a-byte-array-using-binary
MyObject obj = new MyObject();
byte[] bytes;
IFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
formatter.Serialize(stream, obj);
bytes = stream.ToArray();
}
這種方式支持任意的object進行序列化,不過有一個問題,它和Type模型嚴格系結,只支持同一個程式集版本的訊息互動,也不支持其他語言撰寫的程式,
我之前用過這種方式,用于單個程式內的資料快速保存與讀取,這種情況下,只是單純在為了保存object的狀態,操作非常便捷,我認為非常合適,
Protobuf
這個東西就是grpc中的資料格式,可以跨平臺,支持多種語言,資料是二進制的,壓縮率也很高,好吧,就是它了,
如果要在.NET中使用Protobuf協議,經常用的兩個類別庫,一個是Google.Protobuf,另外一個是protobuf.net,詳細的區別我就不贅述了,有一篇文章有多個對比,由于我比較喜歡直接使用C#的型別系統,所以我還是聽從文章建議,直接使用protobuf.net了,
protobuf-net
對于通信雙方都是.NET程式的情況下,使用protobuf不需要直接撰寫proto檔案,可以直接共享資料類的參考,如果是需要與非.NET程式進行通信的話,也可以通過工具生成,直接從proto中讀取資訊并生成類,回顧一下目標,一條條處理,
- 支持任意物件的序列化
protobuf通過定義物體類來進行序列化,所以也是支持任意物件的,這里我就不再詳細說明了,可以在官網查看詳細使用方法,
- 支持從類似System.String的字串中獲取類的資訊并進行反序列化
一直有一個痛點,能否從序列化后的內容中還原一般物件,就是物件型別在編譯的時候未知的那種,通過保存型別的string名稱,在需要反序列化的時候,通過型別名稱加載型別,將內容反序列化為指定型別,這個多多少少要用到反射了吧,
static void Main(string[] args)
{
var ps = new List<string> { "1346dfg" , "31461sfghj", "24576sth"} ;
var name = ps.GetType().FullName;
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Create))
{
Serializer.Serialize(ms, ps);
}
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Open))
{
//data已經轉換為List<string>物件,不過回傳的型別還是object,可以強制轉換,
dynamic data = https://www.cnblogs.com/podolski/archive/2020/12/22/Serializer.Deserialize(Type.GetType(name), ms);
Console.WriteLine(data[1]);
}
}
這里使用到了一個Type型別的FullName屬性,對于內置型別物件,假設ps的型別是String的話,那個FullName為System.String,回傳的內容很簡單,但在這個例子中,FullName為System.Collections.Generic.List`1[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]],感覺一下子復雜了很多,而且要命的是,這里明確指明了CoreLib的參考,還有版本宣告,如果需要在.NET Core3.1中反序列化,肯定是無法實作,
嘗試解決一下,這個System.String不光在.NET 5中有,在其他.NET平臺應該都可以支持,所以得想辦法去掉System.String的尾巴,
可以試著對FullName下手,但是這個東西有點太長了,而且直接處理字串我不是很喜歡;試著從Type型別下手,
var ty = Type.GetType(name);
Console.WriteLine(ty.Name);
Console.WriteLine(ty.Namespace);
Console.WriteLine(ty.GenericTypeArguments[0].Name);
Console.WriteLine(ty.GenericTypeArguments[0].Namespace);
//組合相關的代碼
dynamic data = https://www.cnblogs.com/podolski/archive/2020/12/22/Serializer.Deserialize(Type.GetType($"{ty.Namespace}.{ty.Name}" +
$"[{ty.GenericTypeArguments[0].Namespace}.{ty.GenericTypeArguments[0].Name}]"), ms);
Console.WriteLine(data[1]);
稍微修改一下,通過手動連接Namespace與Name屬性就可以達到我們的目的了,
List`1這個代表這個泛型里面只有一個引數,我這邊就硬編碼了,對于其他泛型,可能有多個引數,需要進行鑒別,并調整構造Type名稱的代碼,
我按照這個思路,完整的代碼如下:
static void Main(string[] args)
{
var ps = new List<string> { "1346dfg", "31461sfghj", "24576sth" };
var ty = ps.GetType();
//保存Type名稱
var name = $"{ty.Namespace}.{ty.Name}" +
$"[{ty.GenericTypeArguments[0].Namespace}.{ty.GenericTypeArguments[0].Name}]";
//實際的程式不涉及檔案操作,這里展示MemoryStream的用法,
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize(ms, ps);
//重置指標,從頭開始讀
ms.Position = 0;
//使用Type名稱反序列化
dynamic data = https://www.cnblogs.com/podolski/archive/2020/12/22/Serializer.Deserialize(Type.GetType(name), ms);
Console.WriteLine(data[1]);
}
}
- 支持簡單物件的直接序列化與反序列化
我說的簡單物件,就是系統定義的泛型集合與直接有TypeCode,并且不是object的物件,補充一下,我常用的幾種,
內置型別
定義在System命名空間下的型別,包括DateTime,Int32之類的,都是直接System.型別名稱的形式,
注意,int和float這種是不行的,需要使用Int32和Single,
泛型集合+內置型別
泛型集合定義在System.Collections.Generic這個命名空間,所以組合起來為System.Collections.Generic.泛型名稱`引數數量[System.型別名稱],舉兩個例子:
List<string>的是System.Collections.Generic.List`1[System.String],
Dictionary<int,string>的是System.Collections.Generic.Dictionary`2[[System.Int32],[System.String]],
內置型別陣列
直接在名稱后添加[]即可,形式為System.型別名稱[],
補充
序列化操作不要求序列化的型別和反序列化的型別完全一致,比如說Array可以與List,IEnumerable進行互換,因此,一些單獨定義的、結構比較簡單的型別,可以通過內置型別進行反序列化,就沒有必要在反序列化的時候加載原始的類了,簡化了操作,
[ProtoContract]
public class Message
{
[ProtoMember(1)]
public List<string> values { get; set; }
}
static void Main(string[] args)
{
var ps = new Message { values = new List<string> { "1346dfg", "31461sfghj", "24576sth" } };
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Create))
{
Serializer.Serialize(ms, ps);
}
using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Open))
{
//List<string>反序列化,而無需使用Message類,這里Message的FullName是"ConsoleApp6.Program+Message"
dynamic data = https://www.cnblogs.com/podolski/archive/2020/12/22/Serializer.Deserialize(Type.GetType("System.Collections.Generic.List`1[System.String]"), ms);
Console.WriteLine(data[1]);
}
}
另外,對于上面的dynamic,由于編譯的時候不檢查,怕操作錯誤的同學可以進行型別轉換,分享一個代碼段,可能能有點幫助,
//轉換物件為某一種型別
public static T ConvertTo<T>(object value)
{
return (T)Convert.ChangeType(value, typeof(T));
}
如果是限定的幾種型別,可以使用switch陳述句進行判斷,并將物件轉成T,以進行型別安全的操作,如果不是的話,推薦使用介面來定義類的通用行為,這個回答中提供了一些建議,推薦看看,
參考資料
- 內置型別
- https://stackoverflow.com/questions/7442164/c-sharp-and-net-how-to-serialize-a-structure-into-a-byte-array-using-binary#
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/238644.html
標籤:.NET技术
